为什么使用易失性与同步块?

我在java中看到了一些例子,他们在代码块上进行同步以更改某些变量,而该变量最初被声明为易失性。我在单例类的一个例子中看到了这一点,他们声明唯一实例为易失性,并且他们对初始化该实例的块进行了同步...我的问题是,为什么我们在同步时声明它是易失性的,为什么我们需要同时做这两件事??难道其中一个对另一个来说还不够吗?

public class SomeClass {
    volatile static Object uniqueInstance = null;

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

提前致谢。


答案 1

在这种情况下,如果第一个检查位于同步块内,则同步本身就足够了(但事实并非如此,如果变量不是易失性的,则一个线程可能不会看到另一个线程执行的更改)。仅靠易失性是不够的,因为您需要以原子方式执行多个操作。但要小心!您在这里拥有的是所谓的双重检查锁定 - 一个常见的成语,不幸的是,它不能可靠地工作。我认为自Java 1.6以来,这种情况已经发生了变化,但这种代码仍然可能有风险。

编辑:当变量是易失性的时,此代码自 JDK 5(不是我之前写的 6)起正常工作,但在 JDK 1.4 或更早版本下,它不会按预期工作。


答案 2

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

如果不是易失性,则它可能使用部分构造的对象“初始化”,其中部分对象对块中执行的线程以外的其他对象不可见。在这种情况下,volatile 使此操作成为全操作或全无操作。uniqueInstancesynchronized

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

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

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

严格来说,你不需要挥发性,方法可能是

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

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