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

时间:2012-03-12 10:35:40

标签: java multithreading volatile synchronized double-checked-locking

我在java中看到了一些例子,他们在一块代码上做同步来改变一些变量,而这个变量最初被声明为volatile。我在单例类的例子中看到了这一点,他们将唯一的实例声明为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;
    }

提前感谢。

5 个答案:

答案 0 :(得分:18)

如果第一次检查在synchronized块内,则本身同步本身就足够了(但如果变量不是volatile,则一个线程可能看不到另一个线程执行的更改)。单独使用Volatile是不够的,因为您需要以原子方式执行多个操作。但要小心!你所拥有的是所谓的双重锁定 - 一种常见的习语,不幸的是does not work reliably。我认为自Java 1.6以来这已经发生了变化,但这种代码仍然存在风险。

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

答案 1 :(得分:6)

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

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

如果您没有同步块,最终可能会有2个线程同时到达此点。

if(uniqueInstance == null) {
      uniqueInstance = new someClass(); <---- here

你构造了2个SomeClass对象,这违背了目的。

严格地说,你不需要volatile,方法可能就是

public static someClass getInstance() {
    synchronized(FullDictionary.class) {
         if(uniqueInstance == null) {
             uniqueInstance = new someClass();
          }
         return uniqueInstance;
    }
}

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

答案 2 :(得分:4)

This post解释了volatile背后的想法。

在开创性工作Java Concurrency in Practice中也提到了这一点。

主要思想是并发不仅涉及保护共享状态,还涉及线程之间状态的可见性:这是volatile进入的地方。(这个更大的契约由Java Memory Model定义。)

答案 3 :(得分:0)

您可以在不使用synchronized块的情况下执行同步。 它不是必须在其中使用volatile变量... volatile从主内存中更新一个变量 synchronized更新已从主内存访问的所有共享变量。 所以你可以根据你的要求使用它。

答案 4 :(得分:-2)

我的两分钱

快速解释这段代码的直觉

if(uniqueInstance == null) {
        synchronized(someClass.class) {
            if(uniqueInstance == null) {
                uniqueInstance = new someClass();
            }
        }
    }

它检查uniqueInstance == null两次的原因是为了减少调用相对较慢的synchronized块的开销。所谓的双重检查锁定。

其次,它使用synchronized的原因很容易理解,它使同步块内的两个操作成为原子。

最后,volatile修饰符确保所有线程都看到相同的副本,因此在synchronized块之外的第一个检查将以一种“同步”的方式查看uniqueInstance的值。 使用synchronized块。如果没有volatile修饰符,则一个线程可以为uniqueInstance分配一个值,但是第一个检查可能看不到另一个线程。 (虽然第二次检查会看到它)

相关问题