关于java:getter和setter应该同步吗?

Should getters and setters be synchronized?

1
2
3
4
5
6
7
8
private double value;

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

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


我认为在实践中引用Java并发是最好的:

It is a common mistake to assume that synchronization needs to be used only when writing to shared variables; this is simply not true.

For each mutable state variable that may be accessed by more than one
thread, all accesses to that variable must be performed with the same
lock held. In this case, we say that the variable is guarded by that
lock.

In the absence of synchronization, the compiler, processor, and runtime can do some downright weird things to the order in which operations appear to execute. Attempts to reason about the order in which memory actions"must" happen in insufflciently synchronized multithreaded programs will almost certainly be incorrect.

通常,您不必对原语那么小心,因此如果这是一个intboolean,可能是:

When a thread reads a variable without synchronization, it may see a
stale value, but at least it sees a value that was actually placed
there by some thread rather than some random value.

但是,对于64位操作来说,这是不正确的,例如在longdouble上,如果它们没有声明volatile

The Java Memory Model requires fetch and
store operations to be atomic, but for nonvolatile long and double
variables, the JVM is permitted to treat a 64-bit read or write as two
separate 32-bit operations. If the reads and writes occur in different
threads, it is therefore possible to read a nonvolatile long and get
back the high 32 bits of one value and the low 32 bits of another.

Thus, even if you don't care about stale values, it is not safe to use
shared mutable long and double variables in multithreaded programs
unless they are declared volatile or guarded by a lock.


让我举例说明什么是JIT编译代码的合法方法。你写:

1
2
3
4
while (myBean.getValue() > 1.0) {
  // perform some action
  Thread.sleep(1);
}

JIT编译:

1
2
3
4
5
if (myBean.getValue() > 1.0)
  while (true) {
    // perform some action
    Thread.sleep(1);
  }

在稍有不同的情况下,即使Java编译器也可以提供类似的字节码(它只需要消除对不同的EDCOX1(5)的动态调度的可能性)。这是一个典型的起重例子。

为什么这是合法的?编译器有权假定执行上述代码时,myBean.getValue()的结果永远不会改变。在没有synchronized的情况下,允许忽略其他线程的任何操作。


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

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

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

由于没有复合语句,因此使字段不稳定将在这里工作。

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

在执行非原子64位操作时,应特别考虑。在实践中,Java并发的摘录可能有助于理解这种情况。

"Java内存模型要求获取和存储操作是原子的,但是对于非易失性长和双变量,JVM允许将64位读写作为两个独立的32来对待。位操作。如果读写发生在不同的线程中,那么就有可能读取非易失性long,并返回一个值的高32位和另一个值的低32位。因此,即使你不关心过时的价值观,它除非声明,否则在多线程程序中使用共享可变长变量和双变量是不安全的易挥发或有锁保护。"


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  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;
    }
  }