我已经为测试实现了一个
FixedClockScheduledExecutorService
,希望对某些人有所帮助。使用它很简单,只需像往常一样安排任务,并在需要推进时间并使事情发生时调用
elapse()
即可。
(有几个小警告:排序未实现,关闭逻辑也未实现,而且可能不适用于极端时间范围。如果必要,这些都可以修复)。
class FixedClockScheduledExecutorService extends AbstractExecutorService implements ScheduledExecutorService {
public FixedClockScheduledExecutorService() {}
private final Collection<Job<?>> jobs = new CopyOnWriteArrayList<>();
private long offsetNanos = 0;
public void elapse(long time, TimeUnit timeUnit) {
offsetNanos += NANOSECONDS.convert(time, timeUnit);
for(Job<?> job: jobs) {
if(offsetNanos >= job.initialDelayNanos) {
jobs.remove(job);
job.run();
}
}
}
private <V> ScheduledFuture<V> scheduleIntenal(Callable<V> callable, long delay, long period, TimeUnit timeUnit) {
Job<V> job = new Job<V>(callable, offsetNanos + NANOSECONDS.convert( delay, timeUnit), NANOSECONDS.convert(period, timeUnit));
jobs.add(job);
return job;
}
@Override
public ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit timeUnit) {
return schedule(Executors.callable(runnable, null), delay, timeUnit);
}
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit timeUnit) {
return scheduleIntenal(callable, delay, 0, timeUnit);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, long delay, long period, TimeUnit timeUnit) {
return scheduleIntenal(Executors.callable(runnable, null), delay, Math.abs(period), timeUnit);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable runnable, long delay, long period, TimeUnit timeUnit) {
return scheduleIntenal(Executors.callable(runnable, null), delay, -Math.abs(period), timeUnit);
}
class Job<V> extends FutureTask<V> implements ScheduledFuture<V> {
final Callable<V> task;
final long initialDelayNanos;
final long periodNanos;
public Job(Callable<V> runner, long initialDelayNanos, long periodNanos) {
super(runner);
this.task = runner;
this.initialDelayNanos = initialDelayNanos;
this.periodNanos = periodNanos;
}
@Override public long getDelay(TimeUnit timeUnit) {return timeUnit.convert(initialDelayNanos, NANOSECONDS);}
@Override public int compareTo(Delayed delayed) {throw new RuntimeException();}
@Override public void run() {
if(periodNanos == 0) {
super.run();
} else {
if(super.runAndReset()) {
jobs.add(reschedule(offsetNanos));
}
}
}
private Job<V> reschedule(long offset) {
if(periodNanos < 0) return new Job<V>(task, offset, periodNanos);
long newDelay = initialDelayNanos; while(newDelay <= offset) newDelay += periodNanos;
return new Job<V>(task, newDelay, periodNanos);
}
}
@Override public void execute(Runnable command) { schedule(command, 0, NANOSECONDS); }
@Override public void shutdown() {}
@Override public List<Runnable> shutdownNow() { throw new RuntimeException(); }
@Override public boolean isShutdown() { return false;}
@Override public boolean isTerminated() { return false;}
@Override public boolean awaitTermination(long timeout, TimeUnit unit) { return true; }
}