【详解】多线程下的单例模式
多线程下单例模式的问题
- 多线程情况懒加载的单例模式,会导致多个线程同时加载对象
- 此时可以采用二次检测的机制,
实现对于性能以及安全的保障
- 使用volatile关键字,可以保证可见性以及避免了各种优化造成的空指针问题,防止初始化
实例写数据还没有完成,造成此时读数据的不准去
解决一:二次检测解决
public class SingletonObject2 {
private static volatile SingletonObject2 instance ;
private SingletonObject2(){
//empty
}
public static SingletonObject2 getInstance(){
if (null == instance){//两次检测的方式实现多线程情况下的懒加载
synchronized (SingletonObject2.class){
if (null == instance) {
instance = new SingletonObject2();
}
}
}
return SingletonObject2.instance;
}
}
注意:<mark>如果不加volatile关键字,可能会出现空指针异常</mark>
原因:
- 如果实例化SingletonObject2时,在构造方法中可能需要构造其他的实例,如Obj1,Obj2
- JVM创建的顺序可能是:
-
Obj1 -> Obj2 -> SingletonObject2
- 但是如果不加volatile关键字,可能会出现重排序,出现:
-
SingletonObject2->Obj1 ->Obj2
解决二:优雅的方式:Holder
- 利用static类,将加载实例的方式放在类的属性中
类加载器在没有使用到该类的时候,类里面的属性并没有被加载
,保证了懒加载- static的特性可以
保证JVM创建该属性的时候只会存在一个
,保证了单例 - 由于
没有加锁
,所以性能也可以保证
public class SingletonObject2 {
private SingletonObject2(){
//empty
}
private static class InstanceHolder{
private final static SingletonObject2 instance = new SingletonObject2();
}
public static SingletonObject2 getInstance(){
return InstanceHolder.instance;
}
}
解决三:优雅的方式:枚举类型
- 枚举类型的构造函数只会调用一次,保证了其单例的特性
public class SingletonObject2 {
private SingletonObject2(){
//empty
}
private enum Singleton{
INSTANCE;
private final SingletonObject2 instance;
Singleton(){
instance = new SingletonObject2();
}
public SingletonObject2 getInstance(){
return instance;
}
}
public static SingletonObject2 getInstance(){
return Singleton.INSTANCE.getInstance();
}
}