我在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;
}
提前感谢。
答案 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分配一个值,但是第一个检查可能看不到另一个线程。 (虽然第二次检查会看到它)