为什么将 volatile 与同步块一起使用?
-
12-12-2019 - |
题
我在java中看到了一些例子,它们在代码块上进行同步以更改某些变量,而该变量最初被声明为易失性..我在单例类的示例中看到,他们将唯一实例声明为易失性,并且同步了初始化该实例的块......我的问题是为什么我们在同步时声明它是易失性的,为什么我们需要两者都做?其中之一不足以满足另一个吗?
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;
}
提前致谢。
解决方案
自身在这种情况下,如果第一个检查在同步块内(但它不是并且一个线程可能没有看到另一个线路,如果变量不易挥发,则一个线程可能没有看到更改)。单独挥发性是不够的,因为您需要原子执行多个操作。但要小心!你在这里拥有的是所谓的双重检查锁定 - 一个常见的成语,不幸的是
编辑:当变量易于易变时,此代码自JDK 5(仅在我之前写的6)以来的工作原理正常工作,但它不会在JDK 1.4或更早版本下的预期工作。
其他提示
这使用了双重检查锁定,请注意 if(uniqueInstance == null)
不在同步部分内。
如果 uniqueInstance
不是易失性的,它可能是用部分构造的对象“初始化”的,其中除了在该对象中执行的线程之外,它的某些部分是不可见的 synchronized
堵塞。在这种情况下,易失性使得这是一个全有或全无的操作。
如果没有同步块,则最终可能会出现 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() 的每个线程的同步和序列化。
这篇文章解释了这个想法挥发性背后。 它也被解决了在精英工作中, java同志在练习中。
主要思想是,并发不仅涉及对共享状态的保护,而且还涉及线程之间该状态的可见性:这是valatile进出的地方。(这个较大的合同由 java memory model 。)
您可以在不使用同步块的情况下执行同步。 在其中使用volatile变量是必要的...... volatile更新主存储器的一个变量.AND 同步更新从主内存访问的所有共享变量。 所以你可以根据你的要求使用它..
我的两美分
FRIST快速解释了这个代码的直觉
if(uniqueInstance == null) {
synchronized(someClass.class) {
if(uniqueInstance == null) {
uniqueInstance = new someClass();
}
}
}
.
它检查的原因是OutceInstance== null两次是减少调用比较较慢的同步块的开销。所以被称为双重检查的锁定。
第二,它使用同步的原因易于理解,它使同步块原子内的两个操作。
持续的易失性修饰符确保所有线程都看到相同的副本,因此同步块外部的第一个检查将以“同步”的方式看到OutfleInstance的值 使用同步块。如果没有易失性修饰符,则一个线程可以将值分配给outformInstance,但另一个线程可能无法通过第一个检查看到它。(虽然第二个检查会看到它)