为什么 java double check lock singleton 必须使用 volatile 关键字?
why java double check lock singleton must use the volatile keyword?
看到过一些类似的问题,但还是有一些疑惑
代码在这里:
private volatile static DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance(){
if(instance==null){ //first
synchronized (DoubleCheckSingleton.class){
if(instance==null){ // second
instance=new DoubleCheckSingleton();
}
}
}
return instance;
}
在这个问题Why is volatile used in double checked locking中,它说如果没有volatile
关键字,一个线程可能会在构造函数完成之前分配实例变量,所以另一个线程可能会看到一个半构造的对象,这可能会导致严重的问题。
但是我不明白volatile是怎么解决问题的。 Volatile
是用来保证可见性的,所以当线程A将一个构造了一半的对象赋值给实例变量时,另一个线程可以立即看到变化,这样就更糟了。
volatile
如何解决这个问题,请哪位大神解释一下。谢谢!
a thread may assign the instance variable before the constuctor finishes
事实并非如此。赋值就在代码示例中:
instance=new DoubleCheckSingleton()
显然,执行该赋值的线程不可能在构造函数调用返回之前完成赋值。
问题是,当两个不同的线程 运行 在两个不同的处理器上没有任何同步时,它们不一定会同意分配发生的顺序。因此,即使线程 A 分配了新对象的字段(在 new DoubleCheckSingleton()
调用内) 在 它分配 instance
之前,线程 B 也可能会看到这些分配-订单。线程 B 可以在看到 new DobuleCheckSingleton()
所做的其他一些事情之前看到对 instance
的分配。
声明 instance
为 volatile
同步线程。 volatile
保证 一切 线程 A 在 之前 它分配了一个 volatile
变量,当线程 B获取 volatile
变量的值。
换句话说,假设我们有两个完美的线程:
- 线程 1:"if instance != null" ... 好吧,它是空的。
- 线程 2:"if instance != null" ... 好的,它是空的。
- 线程 1:创建一个新的 "instance"
- 线程 2:STOMPS ON 线程 1 刚刚创建的值。
... 而且,很有可能,两个线程目前都没有意识到出现了问题。很可能他们两个都认为他们有一个 DoubleCheckSingleton
并且正在按他们应该的方式使用它们......他们都没有意识到有 两个。 (所以,坐下来,看着数据结构随着您的应用程序一头扎进地下而左右被破坏。)
更糟糕的是,这正是 "happens once-in-a-blue-moon." 这种错误,也就是说它永远不会发生在任何开发人员机器上,但它会在您部署到生产环境后五分钟发生... ;-)
volatile
关键字基本上表示 "this storage location, itself is a shared resource." 它是什么。
现在还有:我认为最好的做法是在开始启动线程或子进程之前初始化所有同步对象。所有这些实体现在都可以引用这些现在已经存在的对象并可以使用它们,但是 none 这些玩家曾经创建过它们。
根据 Java Concurrency In Practice 等,双重检查锁定作为一种反模式花费了很多时间。阿尔。当时他们建议改用 Lazy Holder 模式。
https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
Brian Goetz 在此处写了一篇关于 DCL 如何被破坏的好文章 https://www.javaworld.com/article/2074979/double-checked-locking--clever--but-broken.html
正如 StevenC 向我指出的那样,自从 Java 内存模型更新后它将正常工作,尽管如此我认为 Lazy Holder 仍然是一个不错的选择。实施起来很干净,并且避免了挥发性的需要。 DCL 的不当实现将导致难以发现的错误。
看到过一些类似的问题,但还是有一些疑惑
代码在这里:
private volatile static DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance(){
if(instance==null){ //first
synchronized (DoubleCheckSingleton.class){
if(instance==null){ // second
instance=new DoubleCheckSingleton();
}
}
}
return instance;
}
在这个问题Why is volatile used in double checked locking中,它说如果没有volatile
关键字,一个线程可能会在构造函数完成之前分配实例变量,所以另一个线程可能会看到一个半构造的对象,这可能会导致严重的问题。
但是我不明白volatile是怎么解决问题的。 Volatile
是用来保证可见性的,所以当线程A将一个构造了一半的对象赋值给实例变量时,另一个线程可以立即看到变化,这样就更糟了。
volatile
如何解决这个问题,请哪位大神解释一下。谢谢!
a thread may assign the instance variable before the constuctor finishes
事实并非如此。赋值就在代码示例中:
instance=new DoubleCheckSingleton()
显然,执行该赋值的线程不可能在构造函数调用返回之前完成赋值。
问题是,当两个不同的线程 运行 在两个不同的处理器上没有任何同步时,它们不一定会同意分配发生的顺序。因此,即使线程 A 分配了新对象的字段(在 new DoubleCheckSingleton()
调用内) 在 它分配 instance
之前,线程 B 也可能会看到这些分配-订单。线程 B 可以在看到 new DobuleCheckSingleton()
所做的其他一些事情之前看到对 instance
的分配。
声明 instance
为 volatile
同步线程。 volatile
保证 一切 线程 A 在 之前 它分配了一个 volatile
变量,当线程 B获取 volatile
变量的值。
换句话说,假设我们有两个完美的线程:
- 线程 1:"if instance != null" ... 好吧,它是空的。
- 线程 2:"if instance != null" ... 好的,它是空的。
- 线程 1:创建一个新的 "instance"
- 线程 2:STOMPS ON 线程 1 刚刚创建的值。
... 而且,很有可能,两个线程目前都没有意识到出现了问题。很可能他们两个都认为他们有一个 DoubleCheckSingleton
并且正在按他们应该的方式使用它们......他们都没有意识到有 两个。 (所以,坐下来,看着数据结构随着您的应用程序一头扎进地下而左右被破坏。)
更糟糕的是,这正是 "happens once-in-a-blue-moon." 这种错误,也就是说它永远不会发生在任何开发人员机器上,但它会在您部署到生产环境后五分钟发生... ;-)
volatile
关键字基本上表示 "this storage location, itself is a shared resource." 它是什么。
现在还有:我认为最好的做法是在开始启动线程或子进程之前初始化所有同步对象。所有这些实体现在都可以引用这些现在已经存在的对象并可以使用它们,但是 none 这些玩家曾经创建过它们。
根据 Java Concurrency In Practice 等,双重检查锁定作为一种反模式花费了很多时间。阿尔。当时他们建议改用 Lazy Holder 模式。
https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
Brian Goetz 在此处写了一篇关于 DCL 如何被破坏的好文章 https://www.javaworld.com/article/2074979/double-checked-locking--clever--but-broken.html
正如 StevenC 向我指出的那样,自从 Java 内存模型更新后它将正常工作,尽管如此我认为 Lazy Holder 仍然是一个不错的选择。实施起来很干净,并且避免了挥发性的需要。 DCL 的不当实现将导致难以发现的错误。