一个对象是否总是看到其最新的内部状态而与线程无关?

Ran*_*ion 12 java concurrency multithreading

假设我有一个带有简单整数计数变量的可运行对象,该变量在每次可运行对象运行时都会增加。提交此对象的一个​​实例,使其在预定的执行程序服务中定期运行。

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);
Run Code Online (Sandbox Code Playgroud)

在这里,对象正在访问其在不同线程内部的内部状态(读取和递增)。此代码是线程安全的,还是count在其他线程中计划变量时丢失变量的更新?

Sot*_*lis 11

一个对象是否总是看到其最新的内部状态而与线程无关?

为了清楚起见,在此问题及其答案中,一个对象什么也没。这只是记忆。线程是执行实体。说物体看到了什么有误导性。正是线程正在查看/读取对象状态。

在Javadoc中未指定,但是

Executors.newScheduledThreadPool(5);
Run Code Online (Sandbox Code Playgroud)

返回ScheduledThreadPoolExecutor

您的代码正在使用

executorService.scheduleWithFixedDelay(counter, 1, 1, TimeUnit.SECONDS);
Run Code Online (Sandbox Code Playgroud)

状态的JavadocScheduledThreadPoolExecutor#scheduledWithFixedDelay

提交一个周期性操作,该操作将在给定的初始延迟之后首先启用,然后在给定的执行终止与下一个执行之间延迟一定的时间。

javadoc类进一步阐明

Successive executions of a periodic task scheduled via scheduleAtFixedRate or scheduleWithFixedDelay do not overlap. While different executions may be performed by different threads, the effects of prior executions happen-before those of subsequent ones.

As such, each execution of Counter#run is guaranteed to see the value of count after it's been incremented by the previous execution. For example, the third execution will read a count value of 2 before it performs its increment.

You don't need volatile or any other additional synchronization mechanism for this specific use case.


Iva*_*van 7

不,此代码不是线程安全的,因为在以开头的不同线程中进行的增量之间没有任何事前关系ScheduledExecutorService

要修复它,您需要将变量标记为volatile或切换到AtomicIntegerAtomicLong

更新:

正如@BoristheSpider提到的那样,通常在递增/递减时创建变量volatile是不够的,因为递增/递减本身不是原子的,并且同时从多个线程调用它会导致竞争条件和错过的更新。但是,在这种特殊情况下scheduleWithFixedDelay()(根据Javadoc)保证了计划任务的执行会重叠,因此即使在递增的情况下volatile也可以在这种特殊情况下工作。