ScheduledThreadPoolExecutor线程中的可见性



我有一个秒表,它使用内部单线程调度执行器运行指定的时间(duration参数(

public class StopWatch {
private final AtomicBoolean running = new AtomicBoolean();
private final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
private long lastTimeMillis;
private long durationMillis;
private long elapsedMillis;
private ScheduledFuture<?> future;
public void start(long duration, TimeUnit timeUnit) {
if (running.compareAndSet(false, true)) {
durationMillis = timeUnit.toMillis(duration);
lastTimeMillis = System.currentTimeMillis();
elapsedMillis = 0;
future = executor.schedule(this::tick, 0, TimeUnit.MILLISECONDS);
}
}
(...)
private void tick() {
long now = System.currentTimeMillis();
elapsedMillis += now - lastTimeMillis;
lastTimeMillis = now;
// do some stuff
if (elapsedMillis < durationMillis) {
future = executor.schedule(this::tick, 100, TimeUnit.MILLISECONDS);
return;
}
running.compareAndSet(true, false);
}
(...)
}

我的问题是:使用这种方法(即,在第一个周期结束后,在StopWatch上再次执行.start()(,我会遇到任何可见性问题吗?

elapsedMillislastTimeMillis在两个线程中进行更新,durationMillis在第一个线程中更新并在第二个线程中读取,但它是按顺序发生的,并且调度任务在第一个执行完更新字段后开始。不过,我仍然不确定跳过这些领域的波动性是否安全(可能不是(。

除非你真的需要挤出最后一点性能,否则我不会担心上面的代码或多或少会有一个volatile。因此,你的问题的简单答案是:使字段不稳定。

时间表和正在执行的任务之间存在"先发生后执行"的关系。查看此处的内存一致性效果:

https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/concurrent/ExecutorService.html

因此,任务应该能够看到在将任务放置到执行器上之前所做的更改。这是因为先发生后发生关系是可传递的。

您正在做的事情可以通过ScheduledExecutorServiceAPI:更好地完成

public class StopWatch {
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> future;
public void start(long duration, TimeUnit timeUnit) {
if (future == null || future.isDone()) {
future = executor.scheduleAtFixedRate(this::tick, 0L, 100L, TimeUnit.MILLISECONDS);
executor.schedule((Runnable) () -> future.cancel(false), duration, timeUnit);
}
}
private void tick() {
}
}

最新更新