Java程序  |  79行  |  2.87 KB

package org.mockitousage.verification;

import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.MAX_PRIORITY;
import static java.util.concurrent.Executors.newScheduledThreadPool;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.concurrent.locks.LockSupport.parkUntil;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

class DelayedExecution {
    private static final int CORE_POOL_SIZE = 3;
    /**
     * Defines the number of milliseconds we expecting a Thread might need to unpark, we use this to avoid "oversleeping" while awaiting the deadline for
     */
    private static final long MAX_EXPECTED_OVERSLEEP_MILLIS = 50;

    private final ScheduledExecutorService executor;

    public DelayedExecution() {
        this.executor = newScheduledThreadPool(CORE_POOL_SIZE, maxPrioThreadFactory());
    }

    public void callAsync(long delay, TimeUnit timeUnit, Runnable r) {
        long deadline = timeUnit.toMillis(delay) + currentTimeMillis();

        executor.submit(delayedExecution(r, deadline));
    }

    public void close() throws InterruptedException {
        executor.shutdownNow();

        if (!executor.awaitTermination(5, SECONDS)) {
            throw new IllegalStateException("This delayed excution did not terminated after 5 seconds");
        }
    }

    private static Runnable delayedExecution(final Runnable r, final long deadline) {
        return new Runnable() {
            @Override
            public void run() {
                //we park the current Thread till 50ms before we want to execute the runnable
                parkUntil(deadline - MAX_EXPECTED_OVERSLEEP_MILLIS);
                //now we closing to the deadline by burning CPU-time in a loop
                burnRemaining(deadline);

                System.out.println("[DelayedExecution] exec delay = "+(currentTimeMillis() - deadline)+"ms");

                r.run();
            }

            /**
             * Loop in tight cycles until we reach the dead line. We do this cause sleep or park is very not precise,
             * this can causes a Thread to under- or oversleep, sometimes by +50ms.
             */
            private void burnRemaining(final long deadline) {
                long remaining;
                do {
                    remaining = deadline - currentTimeMillis();
                } while (remaining > 0);
            }
        };
    }

    private static ThreadFactory maxPrioThreadFactory() {
        return new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setDaemon(true);  // allows the JVM to exit when clients forget to call DelayedExecution.close()
                t.setPriority(MAX_PRIORITY);
                return t;
            }
        };
    }
}