同步保证线程是否会看到另一个线程修改的非易失性变量的最新值?

时间:2014-03-28 07:31:02

标签: java multithreading concurrency real-time java-memory-model

这是一个简单的例子:

private long counter = 0;

// note this method is NOT synchronized
// this will be called by thread A
public void increment() { counter++; }

// note this method IS synchronized
// this will be called by thread B
public synchronized long value() { return counter; }

所以我只想为counter获得一个好的值,而不是cpu缓存中的卡值,因为该变量是非易失性的。目标是不使计数器变为易失性,因此它不会影响线程A执行增量,但只会在读取变量时影响线程B,我不在乎。

仅仅为了记录,我计划在线程A已经完成时从线程B读取counter的值...

4 个答案:

答案 0 :(得分:2)

long赋值不保证是原子的,所以不仅B可以读取陈旧值,还可以读取半值。

为了获得适当的可见性,您需要使计数器变得易失。请注意,即使这样,从多个线程调用增量n次也不会使计数器增加n。

您可以使用AtomicLong来解决问题。

答案 1 :(得分:2)

不,线程B中的同步块不能确保它将读取counter的实际当前值。您需要两个线程中的同步块才能执行此操作。从实际角度来看,您的代码确保运行线程B的处理器使其缓存无效并从主内存中读取counter的值,但它不能确保运行线程A的处理器将其当前值刷新到主内存,所以主存中的值可能是陈旧的。

由于使用volatile变量比两个线程中的synchronized块便宜,因此counter volatile可能是正确的解决方案。这就是volatile变量的用途。

编辑:如果线程A在线程B读取最终值之前完成,则可以将线程A的整个执行包含在单个同步块中,或者让线程B在读取计数器之前连接线程A,从而确保线程A在读取计数器之前完成。这将导致在线程A执行结束时一次缓存刷新,这对性能的影响可以忽略不计。

答案 2 :(得分:1)

不,synchornized仅保证对同一锁的同步块内所做更改的可见性:

synchornized(this) {
    counter++;
}

或之前(由关系之前发生的传递性质定义):

// Thread A
counter++
synchronized (this) {
    finished = true; 
}

// Thread B
synchonized (this) {
    if (finished) {
        // you can read counter here
    }
}

但是,请注意,如果您在确定线程A已完成(例如,使用counter)之后阅读join(),则threadA.join(); // you can read counter here 保证是可见的:

{{1}}

答案 3 :(得分:0)

否。Thread B无法保证始终提供最新值。因为increment()是非同步方法,而value()是同步方法。
由于

  

当一个线程在一个对象的同步方法中时,所有其他希望执行此同步方法或该对象的任何其他同步方法的线程都必须等待。

     

此限制不适用于已具有锁定并正在执行对象的同步方法的线程。这种方法可以调用对象的其他同步方法而不会被阻塞。当然,任何线程都可以随时调用对象的非同步方法。