Java 为什么在双重检查锁定中使用volatile
从Head First design patterns book中,具有双重检查锁定的单例模式已实现如下:Java 为什么在双重检查锁定中使用volatile,java,design-patterns,locking,singleton,double-checked-locking,Java,Design Patterns,Locking,Singleton,Double Checked Locking,从Head First design patterns book中,具有双重检查锁定的单例模式已实现如下: public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchron
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
我不明白为什么要使用
volatile
。volatile
使用是否违背了使用双重检查锁定(即性能)的目的?如果没有,第二个线程可能会在第一次将其设置为null后进入同步块,并且您的本地缓存仍会认为它为null
第一个不是为了正确性(如果你是正确的,那么它会弄巧成拙),而是为了优化。将变量声明为volatile可以保证所有对它的访问实际上都从内存中读取它的当前值
如果没有
volatile
,编译器可能会优化对变量的内存访问(例如将其值保留在寄存器中),因此只有第一次使用变量时才会读取保存变量的实际内存位置。如果变量被第一次和第二次访问之间的另一个线程修改,则会出现问题;第一个线程只有第一个(预修改的)值的副本,因此第二个if
语句测试变量值的旧副本。了解为什么需要volatile
。Wikipedia也有很多这样的材料
真正的问题是
线程A
可能会在完成实例
的构造之前为实例
分配一个内存空间<代码>线程B将看到该赋值并尝试使用它。这会导致线程B
失败,因为它使用的是部分构造的实例版本
,嗯,没有对性能进行双重检查的锁定。这是一个破碎的模式
撇开情绪不谈,volatile
就在这里,因为当第二个线程通过instance==null
时,如果没有它,第一个线程可能无法构造new Singleton()
然而:除了实际创建对象的线程外,没有人承诺在为任何线程分配到实例之前创建对象
volatile
依次建立读取和写入之间的关系,并修复损坏的模式
如果您想提高性能,请改用holder内部静态类。易失性读取本身并不昂贵
您可以设计一个测试,在紧密循环中调用getInstance()
,以观察易失性读取的影响;然而,这种测试是不现实的;在这种情况下,程序员通常会调用一次getInstance()
,并在使用期间缓存实例
另一个impl是使用final
字段(参见维基百科)。这需要额外的读取,这可能比volatile
版本更昂贵。最终版本在一个严密的循环中可能会更快,但是,正如前面所说的那样,该测试是没有意义的。正如@unreputable,volatile所引用的那样,它并不昂贵。即使代价高昂,一致性也应该优先于性能
对于懒惰的单身汉来说,还有一种更干净优雅的方式
public final class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
来源文章:维基百科
在软件工程中,按需初始化持有者(设计模式)习惯用法是延迟加载的单例。在所有版本的Java中,该习惯用法都支持安全、高度并发的惰性初始化,并具有良好的性能
由于类没有任何要初始化的静态
变量,因此初始化很容易完成
在JVM确定必须执行LazyHolder之前,不会初始化其中的静态类定义LazyHolder
静态类LazyHolder
仅在类Singleton上调用静态方法getInstance
时执行,第一次发生这种情况时,JVM将加载并初始化LazyHolder
类
此解决方案是线程安全的,不需要特殊的语言构造(即volatile
或synchronized
)。双重检查锁定是一种防止在多线程环境中调用getInstance
方法时创建另一个singleton实例的技术
注意
- 在初始化之前,对Singleton实例进行两次检查
- 为了提高性能,只有在第一次检查singleton实例之后才使用Synchronized critical section
- 实例成员声明上的
volatile
关键字。这将告诉编译器始终从主内存而不是CPU缓存进行读取和写入。使用volatile
变量保证在关系之前发生,所有写入操作都将在读取实例变量之前发生
缺点
- 由于它需要
volatile
关键字才能正常工作,因此它与Java 1.4及更低版本不兼容。问题在于,无序写入可能允许在执行单例构造函数之前返回实例引用
- 性能问题,因为易失性变量的缓存下降李>
- 在初始化之前,对Singleton实例进行两次检查
- 它非常冗长,并且使代码难以阅读
单例模式的实现有几种,每种都有其优缺点。
- 急加载单态
- 双重检查锁定单例
- 按需初始化持有者习惯用法
- 基于枚举的单例
详细描述每一个都太冗长了,所以我只是放了一个链接到一篇好文章-我认为双重检查锁定被破坏了,有人修复了吗?值得一提的是,我发现头先设计模式是一本可怕的书,值得学习。当我回首往事时,它变得完美了