问题描述
DCL全称Double Check Lock,描述的是单例模式中降低锁粒度后,使用双重检查的方式保证多线程下的数据一致性。
单例模式
1.饿汉式
初始化类时使用静态变量修饰符定义对象并完成实例化,将默认构造方法设置为私有方法,并定义公共的对象获取方法返回静态对象实例。
package com.example.design_pattern.singleton;
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
if (instance != null) {
throw new RuntimeException("单例不允许创建多个实列");
}
}
public static HungrySingleton getInstance() {
return instance;
}
}
2.懒加载
初始化类时不进行实例化,只有在需要使用并获取该实例对象的时候才进行对象的实例化。该情况下为了保证实例的线程安全,则需要使用锁控制来控制对象的实例化过程。
1.直接使用synchronized对获取实例对象的方法进行加锁
2.缩小锁的范围,只在对象实例化的部分进行加锁操作,此时采用在锁对象前后双重检查的对象是否为空的方式来保证线程安全,这就是所谓的DCL。
DCL是否需要使用volatile
实例化过程在字节码层面的正常顺序为:分配空间>>初始化赋值>>建立引用。在实际编译过程中,JIT可能会对字节码指令进行重新排序,排序后顺序为:分配空间>>建立引用>>初始化赋值。这种情况下DCL是无法保证线程安全的。volatile的用途是禁止指令重排序,从而保证实例对象的线程安全。
package com.example.design_pattern.singleton;
public class LazySingleton {
/**
* volatile防止指令重排导致的问题
*/
private volatile static LazySingleton instance;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
//字节码层,JIT、CPU可能会对指令进行重排,
//正常顺序:1、分配空间,2、初始化赋值,3、建立引用
instance = new LazySingleton();
}
}
}
return instance;
}
}