Java 在使用双重检查锁定实现单例时是否需要volatile
假设我们使用双重检查锁来实现单例模式:Java 在使用双重检查锁定实现单例时是否需要volatile,java,singleton,synchronized,volatile,Java,Singleton,Synchronized,Volatile,假设我们使用双重检查锁来实现单例模式: private static Singleton instance; private static Object lock = new Object(); public static Singleton getInstance() { if(instance == null) { synchronized (lock) { if(instance == nu
private static Singleton instance;
private static Object lock = new Object();
public static Singleton getInstance() {
if(instance == null) {
synchronized (lock) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
我们是否需要将变量“instance”设置为“volatile”?我听到一句话,我们需要它来禁用重新排序:
创建对象时,可能会发生重新排序:
address=alloc
instance=someAddress
init(someAddress)
他们说,如果最后两个步骤被重新排序,我们需要一个volatile实例来禁用重新排序,否则其他线程可能会得到一个未完全初始化的对象
然而,由于我们处于同步代码块中,我们真的需要volatile吗?或者,一般来说,我可以说synchronized block可以保证共享变量对其他线程是透明的,并且即使它不是易失变量也不会重新排序吗?在我开始解释之前,您需要了解编译器所做的一个优化(我的解释非常简单)。假设您的代码中有这样一个序列:
int x = a;
int y = a;
编译器完全可以将它们重新排序为:
// reverse the order
int y = a;
int x = a;
没有人将写入a
此处,只有两次读取a
,因此允许这种类型的重新排序
一个稍微复杂一点的例子是:
// someone, somehow sets this
int a;
public int test() {
int x = a;
if(x == 4) {
int y = a;
return y;
}
int z = a;
return z;
}
编译器可能会查看这段代码,并注意到如果输入了这段代码,if(x==4){…}
,那么这段代码:intz=A代码>从不发生。但是,同时,您可以考虑稍微不同的情况:如果输入了if语句,我们不关心intz=a无论是否执行代码>,它都不会改变以下事实:
int y = a;
return y;
仍然会发生。因此,让我们把intz=a代码>渴望:
public int test() {
int x = a;
int z = a; // < --- this jumped in here
if(x == 4) {
int y = a;
return y;
}
return z;
}
它插入了一个返回实例
,从语义上讲,这不会以任何方式更改代码的逻辑
然后,编译器所做的一些工作将对我们有所帮助。我不打算深入讨论细节,但它引入了一些本地字段(好处在于该链接)来执行所有读写操作(存储和加载)
您可以看到,在所有情况下,这样做都没有害处:singletonlocal4=instance
在任何if检查之前
在经历了所有这些疯狂之后,您的代码可能会变成:
public static Singleton getInstance() {
Singleton local4 = instance; // < --- read (3)
Singleton local1 = instance; // < --- read (1)
if (local1 == null) {
synchronized (lock) {
Singleton local2 = instance; // < --- read (2)
if (local2 == null) {
Singleton local3 = new Singleton();
instance = local3; // < --- write (1)
return local3;
}
}
}
return local4;
}
您将instance
读入local4
(假设为null
),然后将instance
读入local1
(假设某个线程已将其更改为非null)并。。。您的getInstance
将返回一个null
,而不是一个Singleton
。q、 急诊室
结论:只有当私有静态单例实例时,这些优化才可能实现
是非易失性的
,否则很多优化都是被禁止的,这样的优化是不可能的。因此,是的,使用volatile
是此模式正常工作的必要条件 这些信息的来源在哪里?只是同事们的讨论。真正的问题是,为什么我们要对单例使用双重检查锁定(而不是enum
或其他实现方法)?真正的问题是:我们需要将这个共享实例变量设置为“volitile”吗?这似乎也是多线程的问题。如果我们有两个动作x和y,我们写hb(x,y)来表示x发生在y之前。如果x和y是同一线程的动作,并且x在程序顺序中位于y之前,那么hb(x,y)。还可以看到更可怕的是,如果你看到一个未完全构造的对象。毕竟,如果不是易失性的,编译器可能会将构造函数中的存储重新排序为实例
。@JohannesKuhn是的,你说的是安全发布,为了正确使用易失性
,读写器必须在同一易失性
上成对工作。
private static Singleton instance; // non-volatile
public static Singleton getInstance() {
if (instance == null) { // < --- read (1)
synchronized (lock) {
if (instance == null) { // < --- read (2)
instance = new Singleton(); // < --- write
}
}
}
return instance; // < --- read (3)
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new Singleton();
// < --- we added this
return instance;
}
}
}
return instance;
}
public static Singleton getInstance() {
Singleton local1 = instance; // < --- read (1)
if (local1 == null) {
synchronized (lock) {
Singleton local2 = instance; // < --- read (2)
if (local2 == null) {
Singleton local3 = new Singleton();
instance = local3; // < --- write (1)
return local3;
}
}
}
Singleton local4 = instance; // < --- read (3)
return local4;
}
if (local1 == null) { ... } NOT ENTERED => NEED to do : Singleton local4 = instance
if (local1 == null) { ... } ENTERED && if (local2 == null) { ... } NOT ENTERED
=> MUST DO : Singleton local4 = instance.
if (local1 == null) { ... } ENTERED && if (local2 == null) { ... } ENTERED
=> CAN DO : Singleton local4 = instance. (remember it does not matter if I do it or not)
public static Singleton getInstance() {
Singleton local4 = instance; // < --- read (3)
Singleton local1 = instance; // < --- read (1)
if (local1 == null) {
synchronized (lock) {
Singleton local2 = instance; // < --- read (2)
if (local2 == null) {
Singleton local3 = new Singleton();
instance = local3; // < --- write (1)
return local3;
}
}
}
return local4;
}
Singleton local4 = instance; // < --- read (3)
Singleton local1 = instance; // < --- read (1)
if(local1 == null) {
....
}
return local4;