java - 在 ScheduledThreadPoolExecutor 线程中的可见性

java - visibility in ScheduledThreadPoolExecutor threads

我有一个秒表,它使用内部单线程调度执行程序 运行s 指定时间量(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 在第一个线程中更新并在第二个线程中读取,但它是按顺序发生的,计划任务在第一个线程之后开始完成更新字段。我仍然不确定跳过这些领域的波动性是否安全(可能不安全)。

通过 ScheduledExecutorService API:

可以更好地完成您正在做的事情
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() {

    }
}

除非你真的需要榨取最后一点性能,否则我不会担心上面代码中或多或少的volatile。因此,您问题的简单答案是:使字段可变。

并且计划和正在执行的任务之间存在先行关系。在此处查看内存一致性效果:

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

所以任务应该能够看到在任务被放置到执行器之前所做的更改。这是因为 happens before 关系是可传递的。