Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么在双重检查锁定中使用volatile_Java_Design Patterns_Locking_Singleton_Double Checked Locking - Fatal编程技术网

Java 为什么在双重检查锁定中使用volatile

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

从Head First design patterns book中,具有双重检查锁定的单例模式已实现如下:

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实例进行两次检查
  • 它非常冗长,并且使代码难以阅读
单例模式的实现有几种,每种都有其优缺点。
  • 急加载单态
  • 双重检查锁定单例
  • 按需初始化持有者习惯用法
  • 基于枚举的单例

详细描述每一个都太冗长了,所以我只是放了一个链接到一篇好文章-

我认为双重检查锁定被破坏了,有人修复了吗?值得一提的是,我发现头先设计模式是一本可怕的书,值得学习。当我回首往事时,它变得完美了