为什么使用带有synchronized块的volatile?

Dor*_*ham 42 java multithreading volatile synchronized double-checked-locking

我在java中看到了一些例子,他们在一块代码上做同步来改变一些变量,而这个变量最初被声明为volatile.我在单例类的一个例子中看到,他们将唯一的实例声明为volatile并且他们同步了块初始化那个实例...我的问题是为什么我们在同步它时声明它是不稳定的,为什么我们需要同时做两个?对其他人来说不是其中之一吗?

public class someClass {
volatile static uniqueInstance = null;

public static someClass getInstance() {
        if(uniqueInstance == null) {
            synchronized(someClass.class) {
                if(uniqueInstance == null) {
                    uniqueInstance = new someClass();
                }
            }
        }
        return uniqueInstance;
    }
Run Code Online (Sandbox Code Playgroud)

提前致谢.

Mic*_*ski 19

在这种情况下,如果第一次检查在同步块内,则同步本身就足够了(但如果变量不是易失性的话,一个线程可能看不到另一个线程执行的更改).单独使用Volatile是不够的,因为您需要以原子方式执行多个操作.但要小心!你所拥有的是所谓的双重检查锁定 - 一种常见的习惯用法,遗憾的是它不能可靠地工作.我认为自Java 1.6以来这已经发生了变化,但这种代码仍然存在风险.

编辑:当变量是易失性时,此代码自JDK 5起正确工作(不像我之前写的那样6),但它在JDK 1.4或更早版本中无法正常工作.

  • 正如对未来读者的预示:上面链接的文章已经过时了,正如编辑所述,该技术在大约10年前发布的JDK 5上运行良好. (4认同)
  • 你说如果第一次检查在同步块内,同步就足够了....但是在进入同步块后我再次进行相同的检查,所以确保下一个线程会看到变量的更新值. (2认同)
  • @MohammadDorgham @MichałKosmulski 第二个空检查将始终看到更新的值。那么这是否意味着 `volatile` 仅用于使第一次空检查失败以提高效率?(没有 `volatile` 它会很慢但仍然正确?) (2认同)
  • 来自 Oracle 文档“当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立先发生关系。这保证了对象状态的更改对所有线程都是可见的”https:/ /docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html。我认为同步块也是如此。如果为 true,则被阻止的线程应该会看到“uniqueInstance”的更新值。 (2认同)

nos*_*nos 7

这使用双重检查锁定,请注意它if(uniqueInstance == null)不在同步部分内.

如果uniqueInstance不是volatile,它可能会被部分构造的对象"初始化",其中部分对象不在synchronized块中执行的线程可见.volatile在这种情况下使这成为全有或全无操作.

如果你没有synchronized块,那么最终可能会有2个线程同时到达这一点.

if(uniqueInstance == null) {
      uniqueInstance = new someClass(); <---- here
Run Code Online (Sandbox Code Playgroud)

并且你构造了2个SomeClass对象,这违背了目的.

严格来说,你不需要挥发性,方法本来可以

public static someClass getInstance() {
    synchronized(FullDictionary.class) {
         if(uniqueInstance == null) {
             uniqueInstance = new someClass();
          }
         return uniqueInstance;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是这会导致执行getInstance()的每个线程的同步和序列化.


Mic*_*ter 6

这篇文章解释了 volatile 背后的想法。

它也在开创性工作Java Concurrency in Practice 中得到解决

主要思想是并发不仅涉及共享状态的保护,还涉及线程之间该状态的可见性:这就是 volatile 的用武之地。(这个更大的契约由Java 内存模型定义。)