private double value;
public synchronized void setValue(double value) {
this.value = value;
}
public double getValue() {
return this.value;
}
在上面的例子中,让getter同步有什么意义吗?
答案 0 :(得分:71)
我认为最好在这里引用Java Concurrency in Practice:
假设只有在写入共享变量时才需要使用同步,这是一个常见的错误。这根本不是真的。
对于可由多个访问的每个可变状态变量 线程,对该变量的所有访问必须使用相同的线程执行 锁定。在这种情况下,我们说变量是由它保护的 锁。
在没有同步的情况下,编译器,处理器和运行时可以对操作似乎执行的顺序做一些彻头彻尾的奇怪事情。尝试推理在不完全同步的多线程程序中“必须”发生内存操作的顺序几乎肯定是不正确的。
通常情况下,您不必非常小心原语,因此如果这是int
或boolean
,则可能是:
当线程在没有同步的情况下读取变量时,它可能会看到一个 陈旧的价值,但至少它看到了实际放置的价值 有一些线索而不是一些随机值。
但是,对于64位操作,例如long
或double
,如果未声明volatile
,则不是这样:
Java内存模型需要fetch和 存储操作是原子的,但对于非易失性的长和双 变量,允许JVM将64位读取或写入视为两个 单独的32位操作。如果读写发生在不同的 线程,因此可以读取非易失性long并得到 返回一个值的高32位和另一个值的低32位。
因此,即使您不关心过时值,也不安全 在多线程程序中共享可变长和双变量 除非他们被宣布为挥发性或被锁防守。
答案 1 :(得分:15)
让我通过示例向您展示JIT编译代码的合法方式。你写道:
while (myBean.getValue() > 1.0) {
// perform some action
Thread.sleep(1);
}
JIT编译:
if (myBean.getValue() > 1.0)
while (true) {
// perform some action
Thread.sleep(1);
}
在稍微不同的场景中,即使Java编译器也可以产生类似的字节码(它只需要消除动态调度到不同getValue
的可能性)。这是一个提升的教科书例子。
为什么这是合法的?编译器有权假设myBean.getValue()
的结果在执行上述代码时永远不会改变。如果没有synchronized
,则允许忽略其他线程的任何操作。
答案 2 :(得分:1)
这里的原因是为了防止线程正在读取时更新值的任何其他线程,从而避免对过时值执行任何操作。
这里get方法将获得内部锁定"这个"因此,任何其他可能尝试使用setter方法设置/更新的线程都必须等待获取锁定"这个"输入已通过线程执行get获取的setter方法。
这就是为什么建议在可变状态下执行任何操作时遵循使用相同锁定的做法。
将字段设置为volatile将在此处起作用,因为没有复合语句。
重要的是要注意同步方法使用内部锁定,这是"这个"。因此,获取和设置两者都是同步意味着进入该方法的任何线程都必须获取锁定。
执行非原子64位操作时,应特别注意。 Java Concurrency In Practice的摘录可以帮助您了解情况 -
" Java内存模型要求获取和存储操作是原子操作,但对于非易失性长和双变量,JVM允许将64位读或写视为两个独立的32 位操作。如果读取和写入发生在不同的线程中,则可以读取非易失性长并且返回一个值的高32位和另一个值的低32位。因此,即使你不关心陈旧的价值观,它也是如此 在多线程程序中使用共享的可变长和双变量是不安全的,除非声明它们 挥动或被锁保护。"
答案 3 :(得分:0)
也许对某些人而言,这段代码看起来很糟糕,但效果非常好。
private Double value;
public void setValue(Double value){
updateValue(value, true);
}
public Double getValue(){
return updateValue(value, false);
}
private double updateValue(Double value,boolean set){
synchronized(MyClass.class){
if(set)
this.value = value;
return value;
}
}