Java并发 - 为什么不同步setter(而不是getter)使类的线程安全?

时间:2010-09-01 07:31:58

标签: java concurrency shared-memory thread-safety

  

可能重复:
  Thread safety in Java class

我正在阅读实践中的Java并发,我找到了一个令我困惑的例子。

作者声明这个类不是线程安全的

public class MutableInteger {

    private int number;

    public int getInt() {
        return number;
    }

    public void setInt(int val) {
        number = val;
    }
}

并且他们还声明只同步一个方法(例如setter)不会;你必须同步两个。

我的问题是:为什么?不会同步setter吗?

4 个答案:

答案 0 :(得分:5)

Java在内存模型之前/之后发生。在写路径和读路径上都需要有一些常见的并发构造(例如,同步块/方法,锁定,易失性,原子)来触发此行为。

如果同步这两个方法,则会在整个对象上创建一个锁,这个锁将由读写线程共享。 JVM将确保在离开(synchronized)setInt方法之前在写入线程上发生的任何更改在进入(synchronized)getInt方法后对任何读取线程都是可见的。 JVM将插入必要的内存屏障以确保这种情况发生。

如果只同步write方法,则对任何读取线程可能看不到对象的更改。这是因为JVM可以使用读取路径来确保读取线程的可见内存(缓存等)与写入线程一致。使getInt方法同步将提供。

注意:特别是在这种情况下,使字段'number'为volatile会产生正确的行为,因为volatile读/写也在JVM中提供相同的内存可见性行为,而setInt方法内部的操作只是一个赋值。 / p>

答案 1 :(得分:5)

在样本之前的书中对此进行了解释(第35页):

“仅同步setter是不够的:调用get的线程仍然可以看到陈旧的值。”

陈旧数据:当读者线程检查就绪时,它可能会看到过时的值。除非同时使用每次访问变量,否则可能会看到该变量的陈旧值。更糟糕的是,陈旧不是全有或全无:一个线程可以看到一个变量的最新值,但是第一个写入的另一个变量的陈旧值。

答案 2 :(得分:1)

如果仅同步setter方法,则只能保证不会错误地修改属性,但是当您尝试读取变量时,无法确定它是否为陈旧值。

答案 3 :(得分:-1)

因为number不是易变的,并且getInt()未同步,getInt()可能会返回失效值。有关更多信息,请阅读Java内存模型。