深入探索单例模式singleTon
单例模式:保证在任何类任何方法中,获取到某类实例只有一个
第一种方式
public class LazySingleTon {
private LazySingleTon singleTon;
//私有化构造方法
private LazySingleTon(){}
//对外部提供一个方法,用于获取LazySingleTon实例
public synchronized LazySingleTon getInstance(){
if(singleTon==null){
return singleTon=new LazySingleTon();
}
return singleTon;
};
}
分析:这种方式好吗? 对getInstance()加锁合适吗
-
这种方法能保证实例只有一个,我们的目的达成了.但是考虑效率的话,这种方法怎么样?
-
假设有多个线程调用getInstance()方法,获取LazySingleTon类的实例 . 但是由于该方法被synchronized修饰,一次只能有一个线程调用该方法,那么效率会很低 所以这种方法不推荐
-
在想想getInstance()方法,仅仅只是一个获取实例的方法,我们保证单例确实跟该方法没多大关系… 重点在我们要保证new LazySingleTon()只能运行一次
第二种方式
当T1,T2进入图示位置,new LazySingleTon()会被执行两次.所以还需要改进 这里我们采用双重锁的方法
至此,我们第二种方式完成单例模式勉强算完成
那么为什么算勉强完成了呢? 我们来分析
我们在single=new LazySingleTon()时,过程可以细分为 1. 开辟内存空间 2. 初始化内存空间 3. 内存空间引用赋值给single
2,3步骤都是依赖1进行的,并且2,3步骤顺序是可以打乱的 .我们的cpu,编译器(JIT)对具有这种特征的过程可能进行了指令重排序,一旦进行了指令重排序<看下图>
那该怎么解决?
引入volatile关键字,使得singleTon=new LazySingleTon()这个过程不能被指令重排为多个步骤
public class LazySingleTon {
private volatile LazySingleTon singleTon;
//私有化构造方法
private LazySingleTon(){}
//对外部提供一个方法,用于获取LazySingleTon实例
public LazySingleTon getInstance(){
if(singleTon==null){
synchronized (LazySingleTon.class){
if(singleTon==null){
return singleTon=new LazySingleTon();
//1.开辟内存空间
//3.内存引用赋值给singleTon
//2.初始化内存空间
}
}
}
return singleTon;
};
}
}
}
}
return singleTon;
};
}
至此,懒汉模式完美解决