一般的单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton { private static Singleton uniqueSingleton;
private Singleton() { }
public Singleton getInstance() { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } return uniqueSingleton; } }
|
但是在多线程情况下,可能会导致多个实例:
| Time |
Thread A |
Thread B |
| T1 |
检查到uniqueSingleton为空 |
|
| T2 |
|
检查到uniqueSingleton为空 |
| T3 |
|
初始化对象A |
| T4 |
|
返回对象A |
| T5 |
初始化对象B |
|
| T6 |
返回对象B |
|
这种场景,就会创建两次对象。
加锁
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton { private static Singleton uniqueSingleton;
private Singleton() { }
public synchronized Singleton getInstance() { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } return uniqueSingleton; } }
|
这种写法也存在问题,加锁的粒度太大了,只有在创建对象时才需要加锁,后续获取对象时并不需要加锁。
双重锁检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Singleton { private static Singleton uniqueSingleton;
private Singleton() { }
public Singleton getInstance() { if (null == uniqueSingleton) { synchronized (Singleton.class) { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } } } return uniqueSingleton; } }
|
这样写,顺序就变成下面这样:
- 先判断对象是否存在,不存在则加锁。
- 加完锁之后再次判断对象是否存在。
- 不存在则创建。
双重加锁是因为如果多个线程同时通过了第一次判断,那么这几个线程中会有一个线程加锁成功,然后创建对象,后续线程在获取锁之后,就不用再继续创建对象。
存在的隐患
在实例化一个对象时,可以分为以下步骤:
- 分配内存空间
- 初始化对象
- 将对象指向刚分配的内存空间
但是编译器在为了执行速度,可能会进行指令重排序,那么顺序就有可能如下:
- 分配内存空间
- 将对象指向刚分配的内存空间
- 初始化对象
现在考虑如下场景:
| Time |
Thread A |
Thread B |
| T1 |
检查到uniqueSingleton为空 |
|
| T2 |
获取锁 |
|
| T3 |
第二次检查uniqueSingleton为空 |
|
| T4 |
为uniqueSingleton分配空间 |
|
| T5 |
将uniqueSingleton指向内存空间 |
|
| T6 |
|
检查到uniqueSingleton不为空 |
| T7 |
|
访问uniqueSingleton(此时对象还未完成初始化) |
| T8 |
初始化将uniqueSingleton |
|
这种情况下,线程B就访问到了一个还未初始化完成的对象。
解决办法
使用volatile关键字禁止指令重排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Singleton { private volatile static Singleton uniqueSingleton;
private Singleton() { }
public Singleton getInstance() { if (null == uniqueSingleton) { synchronized (Singleton.class) { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } } } return uniqueSingleton; } }
|