我正在阅读实践中的Java并发,我找到了一个令我困惑的例子。
作者声明这个类不是线程安全的
public class MutableInteger {
private int number;
public int getInt() {
return number;
}
public void setInt(int val) {
number = val;
}
}
并且他们还声明只同步一个方法(例如setter)不会;你必须同步两个。
我的问题是:为什么?不会同步setter吗?
答案 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内存模型。