目录
- 由来
- 非线程安全懒汉式单例
- demo
- 存在问题
- 升级版懒汉式单例
- demo
- 存在问题
- 双重检查版本懒汉式单例
- demo
- 存在问题
- 原因分析
- 解决方案
- 不允许重排(volatile)
- 允许重排,但不允许其他线程"看见"这个重排序
1. 由来
在Java程序中,有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化。此时,程序员可能会采用延迟初始化,也就是懒汉式单例
2. 非线程安全懒汉式单例
2.1 demo
/**
* @program: concurrent
* @description: 非线程安全懒汉单例
* @author: chengqj
* @create: 2018-12-06 20:27
**/
public class UnsafeLazySingle {
private static UnsafeLazySingle instance;
public static UnsafeLazySingle getInstance() {
if (instance == null) // 1:A线程执行
{
instance = new UnsafeLazySingle(); // 2:B线程执行
}
return instance;
}
}
###2.2 存在问题
当线程A执行1方法时,2方法还没有执行完成,导致两次获取到的对象不是同一个对象
3. 升级版懒汉式单例
3.1 demo
/**
* @program: concurrent
* @description: 线程安全懒汉单例
* @author: chengqj
* @create: 2018-12-06 20:27
**/
public class SafeLazySingle {
private static SafeLazySingle instance;
private SafeLazySingle(){}
public synchronized static SafeLazySingle getInstance() {
if (instance == null) // 1:A线程执行
{
instance = new SafeLazySingle(); // 2:B线程执行
}
return instance;
}
}
3.2 存在问题
由于对getInstance()方法做了同步处理,synchronized将导致性能开销。如果getInstance()方法被多个线程频繁的调用,将会导致程序执行性能的下降
4. 双重检查版本懒汉式单例
4.1 demo
/**
* @program: concurrent
* @description: 双重检查
* @author: chengqj
* @create: 2018-12-06 20:33
**/
public class DoubleCheckedLocking {
private static DoubleCheckedLocking instance;
private DoubleCheckedLocking(){}
public static DoubleCheckedLocking getInstance() {
// 1:第一次检查
if (instance == null) {
// 2:加锁
synchronized (DoubleCheckedLocking.class) {
// 3:第二次检查
if (instance == null)
// 4:问题的根源出在这里
instance = new DoubleCheckedLocking();
}
}
return instance;
}
}
4.2 存在问题
当线程A执行第3步进行第二次检查的时候,线程B执行第4步的对象还没有完全创建完成。
4.3 原因分析
创建对象过程:
memory = allocate(); // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory; // 3:设置instance指向刚分配的内存地址
实际执行过程:2,3进行了重排序
memory = allocate(); // 1:分配对象的内存空间
instance = memory; // 3:设置instance指向刚分配的内存地址
ctorInstance(memory); // 2:初始化对象
这样就会存在instance对象不为null,但是实际对象没有初始化好。
实际流程:
时间 | 线程A | 线程B |
---|---|---|
t1 | A1:分配对象的内存空间 | |
t2 | A3:设置instance指向刚分配的内存地址 | |
t3 | B1:判断instance是否为空 | |
t4 | B2:由于instance不为空,获取instance对象 | |
t5 | A2:初始化对象 | |
t6 | A4:访问instance引用的对象 |
4.4 解决方案
- 不允许2,3重排序
- 允许2,3重排序,但不允许其他线程"看见"这个重排序
4.5 不允许重排(volatile)
public class DoubleCheckedLocking {
private volatile static DoubleCheckedLocking instance;
private DoubleCheckedLocking(){}
public static DoubleCheckedLocking getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLocking.class) {
if (instance == null)
instance = new DoubleCheckedLocking();
}
}
return instance;
}
}
4.6 允许重排,但不允许其他线程"看见"这个重排序
特性:
JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
/**
* @program: concurrent
* @description: 允许重排,但不允许其他线程"看见"这个重排序
* @author: chengqj
* @create: 2018-12-06 20:33
**/
public class DoubleCheckedLocking {
private static DoubleCheckedLocking instance;
private DoubleCheckedLocking(){}
private static class InstanceHolder {
public static DoubleCheckedLocking instance = new DoubleCheckedLocking();
}
public static DoubleCheckedLocking getInstance() {
return InstanceHolder.instance ;// 这里将导致InstanceHolder类被初始化
}
public static void main(String[] args) {
System.out.println(DoubleCheckedLocking.getInstance());
}
}