Java中的双重检查锁(double checked locking)
2018-02-25 18:09
351 查看
最初的代码
在最近的项目中,写出了这样的一段代码private static SomeClass instance; public SomeClass getInstance() { if (null == instance) { instance = new SomeClass(); } return instance; }
然后在Code Review的时候被告知在多线程的情况下,这样写可能会导致
instance有多个实例。比如下面这种情况:
Time | Thread A | Thread B |
---|---|---|
t1 | A1 检查到instance为空 | |
t2 | B1 检查到instance为空 | |
t3 | B2 初始化对象 | |
t4 | A2 初始化对象 |
第一次的解决方案
于是就想到了为这个方法加锁,如下:private static SomeClass instance; public synchronized SomeClass getInstance() { if (null == instance) { instance = new SomeClass(); } return instance; }
但是又被提醒这样虽然解决了问题,但是会导致很大的性能开销,而加锁只需要在第一次初始化的时候用到,之后的调用都没必要再进行加锁,于是就了解到了双重检查锁(double checked locking)的办法。
第二次的解决方案
private static SomeClass instance; public SomeClass getInstance() { if (null == instance) { // 第一重检查 synchronized (this) { if (null == instance) { // 第二重检查 instance = new SomeClass(); // 这里有问题 } } } return instance; }
这样写的话,运行顺序就成了:
检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量。
获取锁
第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量。否则,初始化并返回变量。
这样,除了初始化的时候会出现加锁的情况,后续的所有调用都会避免加锁而直接返回,从而避免了性能问题,而且看似也解决了同步的问题,然而这样写有个很大的隐患。详细原因如下:
实例化对象的那行代码(标记为有问题的那行),实际上可以分解成以下三个步骤:
分配内存空间
初始化对象
将对象指向刚分配的内存空间
但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
分配内存空间
将对象指向刚分配的内存空间
初始化对象
现在考虑重排序后,发生了以下这种调用:
Time | Thread A | Thread B |
---|---|---|
t1 | A1 检查到instance为空 | |
t2 | A2 获取锁 | |
t3 | A3 再次检查到instance为空 | |
t4 | A4 为instance分配内存空间 | |
t5 | A5 将instance指向内存空间 | |
t6 | B1 检查到instance不为空 | |
t7 | B2 访问instance(对象还未初始化) | |
t8 | A6 初始化instance |
最终的解决方案
为了解决上述问题,可以在instance前加入关键字
volatile。使用了volatile变量后,就能保证先行发生关。对于volatile变量,所有的写(write)都将先行发生于读(read),但在Java5之前不是这样,所以这样的方法只针对Java5及以上的版本。
private volatile static SomeClass instance; public SomeClass getInstance() { if (null == instance) { synchronized (Test.class) { if (null == instance) { instance = new SomeClass(); } } } return instance; }
至此,双重检查锁就可以完美工作了。现在实现单例模式也有了别的更好的办法,但个人觉得这样的坑很有教育意义,故做此记录。
参考资料:
双重检查锁定模式
如何在Java中使用双重检查锁实现单例
双重检查锁定与延迟初始化
相关文章推荐
- 【Java】双重检查锁定(Double-checked locking)与延迟初始化(Initialization on demand holder)
- Java设计模式之单例模式 double---checked----locking双重检查锁定
- 【线程安全】—— 单例类双重检查加锁(double-checked locking)
- Design Pattern_Singleton(单件模式)和Double-Checked Locking(双重检查锁定)
- 深刻理解双重检查锁定(double-checked locking)与单例模式
- 双重检查锁定的漏洞的分析 The "Double-Checked Locking is Broken" Declaration
- 双重检查锁定(double-checked locking)与单例模式
- Singleton 单件模式及其变体 Double-Checked Locking 双重检查锁模式
- Singleton - 单例模式和Double-Checked Locking - 双重检查锁定模式
- 双重检查锁定(double-checked locking)与单例模式
- 双重检查锁定(double-checked locking)与单例模式
- Singleton 单件模式及其变体 Double-Checked Locking 双重检查锁模式
- 双重检查锁定(double-checked locking)与单例模式
- 双重检查锁定失败可能性——参照《The "Double-Checked Locking is Broken" Declaration》
- Double-checked locking and the Singleton pattern--双重检查加锁失效原因剖析
- Singleton(单例)模式和Double-Checked Locking(双重检查锁定)模式
- Java 中的双重检查(Double-Check)
- 解决fortify扫描出的Code Correctness: Double-Checked Locking问题(java语言)
- 【转】Java中的模式 --单态 (部分翻译 double-checked locking break)
- Java中的Double-checked Locking (DCL)问题