摘自Java Concurrency In Practice
安全发布对象的常用模式
- 1.在静态初始化函数中初始化一个对象引用
- 2.将对象的引用保存到volatile类型的域或者AtomicReference对象中
- 3.将对象的引用保存到某个正确构造对象的
final
域中- 4.将对象的引用保存到一个由锁保护的域中
这里介绍一下懒汉模式,它代码实现的演进包含了除了3之外的所有模式。
先看一下单例模式的最简单实现,懒加载,(懒汉模式)。
线程交叉执行会导致它不安全:
@NotThreadSafe
public class LazySingleton {
// 私有构造
private LazySingleton(){ }
private static LazySingleton lazySingleton;
public static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
再看看饿汉模式。如果实例用得次数较少,这种实现可能会导致资源浪费。
@ThreadSafe
public class StarvingSingleton {
private StarvingSingleton() { }
private static StarvingSingleton instance = new StarvingSingleton();
public static StarvingSingleton getInstance(){
return instance;
}
}
synchronized同步函数,会导致多线程并发效率底下。
@ThreadSafe //虽然是线程安全的
@NotRecommend // 但是仍然不推荐
public class LazySynSingleton {
// 私有构造
private LazySynSingleton(){ }
private static LazySynSingleton instance;
// synchronized修饰函数,并发量不大
public static synchronized LazySynSingleton getInstance(){
if(instance == null){
instance = new LazySynSingleton();
}
return instance;
}
}
@NotThreadSafe
public class LazySyn2Singleton {
// 私有构造
private LazySyn2Singleton(){ }
private static LazySyn2Singleton instance;
public static LazySyn2Singleton getInstance(){
if(instance == null){
synchronized (LazySyn2Singleton.class){
if(instance == null){
instance = new LazySyn2Singleton();
}
}
}
return instance;
}
}
多线程下指令重排序
会导致其出问题。
实例化一个对象正常情况下分3步:
- memory = allocate()
开辟一段内存空间
- initInstance()
初始化内存空间(初始化对象)
- instance = memory
实例变量指向内存空间
所以说正常情况会按照1-2-3的顺序来实例化对象。然而,受指令重排序的影响,这3步 很可能会变成1-3-2:
1.memory = allocate()
开辟一段内存空间
3.instance = memory实例变量指向内存空间
2.initInstance()初始化内存空间(初始化对象)
那么很可能会出现下面的情况
if(instance == null){
// 线程B运行到这里
synchronized (LazySyn2Singleton.class){
if(instance == null){
// 线程A运行到这里,但是只运行了第1,3步后
// CPU调度,使线程B可以继续运行
instance = new LazySyn2Singleton();
}
}
}
return instance;
// 那么线程B可以运行到return这里
// 而实际上,线程A并没有将对象初始化
// 所以得到的对象并非一个构造完全的对象
上面的懒汉模式是非线程安全的,因为可能存在指令重排序造成的“实例构造不完全就被溢出”
的危险。
那么我们何不解决这个指令重排序的问题,不让它指令重排序呢?
这就让我们想到了volatile关键字,它能禁止指令重排序!
@NotThreadSafe
public class LazySyn2Singleton {
// 私有构造
private LazySyn2Singleton(){ }
// 使用volatile关键字
private volatile static LazySyn2Singleton instance;
public static LazySyn2Singleton getInstance(){
if(instance == null){
synchronized (LazySyn2Singleton.class){
if(instance == null){
instance = new LazySyn2Singleton();
}
}
}
return instance;
}
}
我们这里使用静态代码块
,能够保证安全性。
@ThreadSafe
@Slf4j
public class Starving2Singleton {
private Starving2Singleton() { }
static {
instance = new Starving2Singleton();
}
private static Starving2Singleton instance;
public static Starving2Singleton getInstance(){
return instance;
}
public static void main(String[] args) {
log.info("Instance1, {}", getInstance());
log.info("Instance2, {}", getInstance());
}
}
如果上面的语句改为
private static Starving2Singleton instance = null;
那么,我们就需要注意static代码块与行内初始化语句的先后顺序了。
相比饿汉模式,它在实际调用时才会初始化,类似懒汉。
相比懒汉模式,它安全性有保证,且代码比较简单。
@ThreadSafe
@Recommend
public class EnumSingleton {
private EnumSingleton() {
}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private EnumSingleton instance;
// JVM保证这个方法绝对只调用一次
Singleton(){
instance = new EnumSingleton();
}
public EnumSingleton getInstance(){
return instance;
}
}
}