70
private double value;

public synchronized void setValue(double value) {
    this.value = value;
}
public double getValue() {
    return this.value;
}

在上面的例子中,使 getter 同步有什么意义吗?

4

4 回答 4

86

我认为最好在这里引用Java Concurrency in Practice

假设仅在写入共享变量时才需要使用同步是一个常见的错误。这是不正确的。

对于可能被多个线程访问的每个可变状态变量,对该变量的所有访问都必须在持有相同的锁的情况下执行。在这种情况下,我们说变量被那个锁保护着。

在没有同步的情况下,编译器、处理器和运行时可以对操作执行的顺序做一些非常奇怪的事情。试图推断在不充分同步的多线程程序中“必须”发生的内存操作的顺序几乎肯定是不正确的。

通常,您不必对原语如此小心,所以如果这将是 anint或 aboolean它可能是:

当一个线程在没有同步的情况下读取一个变量时,它可能会看到一个陈旧的值,但至少它会看到一个由某个线程实际放置在那里的值,而不是某个随机值。

然而,这对于 64 位操作来说是不正确的,例如 onlong或者double如果它们没有被声明volatile

Java 内存模型要求 fetch 和 store 操作是原子的,但对于非易失性 long 和 double 变量,JVM 允许将 64 位读取或写入视为两个单独的 32 位操作。如果读取和写入发生在不同的线程中,则可以读取非易失性 long 并取回一个值的高 32 位和另一个值的低 32 位。

因此,即使您不关心过时的值,在多线程程序中使用共享的可变 long 和 double 变量也是不安全的,除非它们被声明为 volatile 或由锁保护。

于 2012-07-12T19:55:31.933 回答
20

让我通过示例向您展示 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它可以忽略其他线程的任何操作。

于 2012-07-12T20:12:08.217 回答
1

这里的原因是防止任何其他线程在线程读取时更新值,从而避免对陈旧值执行任何操作。

这里 get 方法将在“this”上获得内在锁,因此任何其他可能尝试使用 setter 方法设置/更新的线程都必须等待获得“this”上的锁才能进入执行 get 的线程已经获得的 setter 方法.

这就是为什么它建议在对可变状态执行任何操作时遵循使用相同锁的做法。

由于没有复合语句,因此使字段 volatile 在这里起作用。


重要的是要注意同步方法使用内部锁,即“this”。所以 get 和 set 都是同步的,这意味着任何进入该方法的线程都必须获得锁定。


在执行非原子 64 位操作时,应特别考虑。Java Concurrency In Practice 的摘录可能有助于理解这种情况 -

“Java 内存模型要求 fetch 和 store 操作是原子的,但对于非易失性 long 和 double 变量,JVM 允许将 64 位读取或写入视为两个单独的 32 位操作。如果读取和写入发生在不同的线程, 因此可以读取非易失性 long 并取回一个值的高 32 位和另一个值的低 32 位. 因此, 即使您不关心过时的值, 也不安全在多线程程序中使用共享的可变长和双变量,除非它们被声明为 volatile 或由锁保护。”

于 2014-04-22T07:37:39.797 回答
-3

也许对于某人来说,这段代码看起来很糟糕,但它工作得很好。

  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;
    }
  }
于 2017-05-23T09:05:20.320 回答