JAVA 单例双重检查(double check)为什么不好用
2017-09-27 14:25
253 查看
本文转自:点击打开链接
我假设你已经看过几篇double check的文章,但还是一知半解。
我们先看这种双重检查,不加volatile
2
3
4
5
6
7
8
9
10
11
12
这种方式存在什么问题呢?
也许有人说存在可见性问题:线程1执行完第5步,释放锁。线程2获得锁后执行到第4步,由于可见性的原因,发现instance还是null,从而初始化了两次。
但是不会存在这种情况,因为synchronized能保证线程1在释放锁之前会讲对变量的修改刷新到主存当中,线程2拿到的值是最新的。
实际存在的问题是无序性。
第5步这个new操作是无序的,它可能会被编译成:
- a. 先分配内存,让instance指向这块内存
- b. 在内存中创建对象
然而我们需要意识到这么一个问题,synchronized虽然是互斥的,但不代表一次就把整个过程执行完,它在中间是可能释放时间片的,时间片不是锁。(我因为这里没转过来,耽误了很多时间)
也就是说可能在a执行完后,时间片被释放,线程2执行到1,这时他读到的instance是不是null呢?(标记1)
基于可见性,可能是null,也可能不是null。
非常奇葩的是,在这个例子中,如果读到的是null,反而没问题了,接下来会等待锁,然后再次判断时不为null,最后返回单例。
如果读到的不是null,那么坏了,按逻辑它就直接return instance了,这个instance还没执行构造参数,去做事情的话,很可能就崩溃了。
加volatile
2
3
4
5
6
7
8
9
10
11
12
唯一的区别是加了volatile关键字,那么会有什么现象?
这时要区分jdk版本了,在jdk1.4及之前,volatile并不能保证new操作的有序性,但是它能保证可见性,因此标记1处,读到的不是null,导致了问题。
从1.5开始,加了volatile关键字的引用,它的初始化就不能是:
- a. 先分配内存,让instance指向这块内存
- b. 在内存中创建对象
而应该是:
- a.在内存中创建对象
- b.让instance指向这个对象.
这种形式,也就避免了无序性问题。
JAVA 单例双重检查(double check)为什么不好用
在阅读之前,请先了解下线程并发涉及到的三个概念:原子性、可见性、有序性,可以看下这篇文章:http://www.cnblogs.com/dolphin0520/p/3920373.html我假设你已经看过几篇double check的文章,但还是一知半解。
我们先看这种双重检查,不加volatile
public static Singleton instance; public static Singleton getInstance() { if (instance == null) //1 { //2 synchronized(Singleton.class) { //3 if (instance == null) //4 instance = new Singleton(); //5 } } return instance; }1
2
3
4
5
6
7
8
9
10
11
12
这种方式存在什么问题呢?
也许有人说存在可见性问题:线程1执行完第5步,释放锁。线程2获得锁后执行到第4步,由于可见性的原因,发现instance还是null,从而初始化了两次。
但是不会存在这种情况,因为synchronized能保证线程1在释放锁之前会讲对变量的修改刷新到主存当中,线程2拿到的值是最新的。
实际存在的问题是无序性。
第5步这个new操作是无序的,它可能会被编译成:
- a. 先分配内存,让instance指向这块内存
- b. 在内存中创建对象
然而我们需要意识到这么一个问题,synchronized虽然是互斥的,但不代表一次就把整个过程执行完,它在中间是可能释放时间片的,时间片不是锁。(我因为这里没转过来,耽误了很多时间)
也就是说可能在a执行完后,时间片被释放,线程2执行到1,这时他读到的instance是不是null呢?(标记1)
基于可见性,可能是null,也可能不是null。
非常奇葩的是,在这个例子中,如果读到的是null,反而没问题了,接下来会等待锁,然后再次判断时不为null,最后返回单例。
如果读到的不是null,那么坏了,按逻辑它就直接return instance了,这个instance还没执行构造参数,去做事情的话,很可能就崩溃了。
加volatile
public volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) //1 { //2 synchronized(Singleton.class) { //3 if (instance == null) //4 instance = new Singleton(); //5 } } return instance; }1
2
3
4
5
6
7
8
9
10
11
12
唯一的区别是加了volatile关键字,那么会有什么现象?
这时要区分jdk版本了,在jdk1.4及之前,volatile并不能保证new操作的有序性,但是它能保证可见性,因此标记1处,读到的不是null,导致了问题。
从1.5开始,加了volatile关键字的引用,它的初始化就不能是:
- a. 先分配内存,让instance指向这块内存
- b. 在内存中创建对象
而应该是:
- a.在内存中创建对象
- b.让instance指向这个对象.
这种形式,也就避免了无序性问题。
相关文章推荐
- JAVA 单例双重检查(double check)为什么不好用
- JAVA 单例双重检查(double check)为什么不好用
- Java使用double check(双重检查)实现单例模式的一个小细节
- java单例双重检查锁为什么需要加volatile关键字
- 我的Java开发学习之旅------>Java双重检查锁定及单例模式详解(转)
- Java 中的双重检查(Double-Check)
- Java双重检查锁定
- java:基于volatile和Thread Local Storage的双重检查锁定实现延迟初始化
- java多线程学习(十一) 双重检查锁定和延迟初始化
- Java 中的双重检查(Double-Check)
- 为什么在单例类中不能使用双重检查锁来初始化对象
- java延迟初始化-双重检查锁
- Java 中的双重检查(Double-Check)
- Java中的双重检查锁(double checked locking)
- exe4j的使用-- java -jar test.jar为什么不好用
- java 单例模式(双重检查锁)
- 双重检查锁定在JAVA单例中应用的杯具!
- Java 单例 双重检查锁的正确姿势
- 如何在Java中使用双重检查锁实现单例
- 【Java】单例模式 双重检查锁