public class SimpleSingleton7 { private volatile static SimpleSingleton7 INSTANCE; private SimpleSingleton7() { } public static SimpleSingleton7 getInstance() { if (INSTANCE == null) { synchronized (SimpleSingleton7.class) { if (INSTANCE == null) { INSTANCE = new SimpleSingleton7(); } } } return INSTANCE; } }
如果不添加 volatitle, java虚拟机实际上会做一些优化,对一些代码指令进行重排。 变成:
public static SimpleSingleton4 getInstance() { if (INSTANCE == null) {//1 if (INSTANCE == null) {//3 synchronized (SimpleSingleton4.class) {//2 INSTANCE = new SimpleSingleton4();//4 } } } return INSTANCE;//5 }
详细:
........................................
单例模式有:饿汉模式
和懒汉模式
两种。
饿汉模式代码如下:
public class SimpleSingleton { //持有自己类的引用 private static final SimpleSingleton INSTANCE = new SimpleSingleton(); //私有的构造方法 private SimpleSingleton() { } //对外提供获取实例的静态方法 public static SimpleSingleton getInstance() { return INSTANCE; } }
使用饿汉模式的好处是:没有线程安全的问题,但带来的坏处也很明显。
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
一开始就实例化对象了,如果实例化过程非常耗时,并且最后这个对象没有被使用,不是白白造成资源浪费吗?
还真是啊。
这个时候你也许会想到,不用提前实例化对象,在真正使用的时候再实例化不就可以了?
这就是我接下来要介绍的:懒汉模式
。
具体代码如下:
public class SimpleSingleton2 { private static SimpleSingleton2 INSTANCE; private SimpleSingleton2() { } public static SimpleSingleton2 getInstance() { if (INSTANCE == null) { INSTANCE = new SimpleSingleton2(); } return INSTANCE; } }
示例中的INSTANCE对象一开始是空的,在调用getInstance方法才会真正实例化。
嗯,不错不错。但这段代码还是有问题。
假如有多个线程中都调用了getInstance方法,那么都走到 if (INSTANCE == null) 判断时,可能同时成立,因为INSTANCE初始化时默认值是null。这样会导致多个线程中同时创建INSTANCE对象,即INSTANCE对象被创建了多次,违背了只创建一个INSTANCE对象的初衷。
为了解决饿汉模式
和懒汉模式
各自的问题,于是出现了:双重检查锁
。
具体代码如下:
public class SimpleSingleton4 { private static SimpleSingleton4 INSTANCE; private SimpleSingleton4() { } public static SimpleSingleton4 getInstance() { if (INSTANCE == null) { synchronized (SimpleSingleton4.class) { if (INSTANCE == null) { INSTANCE = new SimpleSingleton4(); } } } return INSTANCE; } }
需要在synchronized
前后两次判空。
但我要告诉你的是:这段代码有漏洞的。
有什么问题?
public static SimpleSingleton4 getInstance() { if (INSTANCE == null) {//1 synchronized (SimpleSingleton4.class) {//2 if (INSTANCE == null) {//3 INSTANCE = new SimpleSingleton4();//4 } } } return INSTANCE;//5 }
getInstance方法的这段代码,我是按1、2、3、4、5这种顺序写的,希望也按这个顺序执行。
但是java虚拟机实际上会做一些优化,对一些代码指令进行重排。重排之后的顺序可能就变成了:1、3、2、4、5,这样在多线程的情况下同样会创建多次实例。重排之后的代码可能如下:
public static SimpleSingleton4 getInstance() { if (INSTANCE == null) {//1 if (INSTANCE == null) {//3 synchronized (SimpleSingleton4.class) {//2 INSTANCE = new SimpleSingleton4();//4 } } } return INSTANCE;//5 }
原来如此,那有什么办法可以解决呢?
答:可以在定义INSTANCE是加上volatile
关键字。具体代码如下:
public class SimpleSingleton7 { private volatile static SimpleSingleton7 INSTANCE; private SimpleSingleton7() { } public static SimpleSingleton7 getInstance() { if (INSTANCE == null) { synchronized (SimpleSingleton7.class) { if (INSTANCE == null) { INSTANCE = new SimpleSingleton7(); } } } return INSTANCE; } }
volatile
关键字可以保证多个线程的可见性
,但是不能保证原子性
。同时它也能禁止指令重排
。
双重检查锁的机制既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。