假设我有一个带有简单整数计数变量的runnable,每次runnable运行时它都会递增。提交此对象的一个实例以在计划的执行程序服务中定期运行。
class Counter implements Runnable {
private int count = 0;
@Override
public void run() {
count++;
}
}
Counter counter = new Counter();
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
executorService.scheduleWithFixedDelay(counter, 1, 1, TimeUnit.SECONDS);
这里,对象在不同线程内部(读取和递增)访问其自己的内部状态。这个代码是否是线程安全的,或者当它在不同的线程中安排时,我们是否可能丢失对count
变量的更新?
无论线程如何,对象总是会看到其最新的内部状态吗?
为了清楚这个问题及其答案的目的,一个对象没有做任何事情;这只是记忆。线程是执行实体。说对象看什么都是误导。它是正在进行对象状态的查看/读取的线程。
这在javadoc中没有指定,但是
Executors.newScheduledThreadPool(5);
返回一个ScheduledThreadPoolExecutor
。
您的代码正在使用
executorService.scheduleWithFixedDelay(counter, 1, 1, TimeUnit.SECONDS);
javadoc for ScheduledThreadPoolExecutor#scheduledWithFixedDelay
说
提交在给定的初始延迟之后首先启用的定期动作,并且随后在一次执行的终止和下一次执行的开始之间给定延迟。
课程javadoc进一步澄清
通过
scheduleAtFixedRate
或scheduleWithFixedDelay
安排的周期性任务的连续执行不重叠。虽然可以通过不同的线程执行不同的执行,但是先前执行的效果发生在后续执行的效果之前。
因此,Counter#run
的每次执行都保证在前一次执行增加之后看到count
的值。例如,第三次执行将在执行增量之前读取count
的2
值。
对于此特定用例,您不需要volatile
或任何其他其他同步机制。
不,这段代码不是线程安全的,因为在使用ScheduledExecutorService
开始的不同线程中进行的增量之间没有任何发生之前的关系。
要修复它,您需要将变量标记为volatile
或切换到AtomicInteger
或AtomicLong
。
更新:
正如@BoristheSpider所提到的,通常在增量/减量的情况下使变量volatile
不够,因为增量/减量不是原子本身并且同时从多个线程调用它将导致竞争条件和错过更新。但是,在这种特殊情况下,scheduleWithFixedDelay()
保证(根据Javadoc)将会有重复执行的计划任务,所以volatile
也会在这个特殊情况下工作,即使有增量。
不,这段代码不是线程安全的,因为访问happens before的不同线程之间没有任何count
关系。