DCL, 即(Double Check Lock),中文:雙重檢查鎖定
1.問(wèn)題分析
我們先看看單例模式里面的懶漢式
public class Singleton{
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if (singleton ==null){
singleton = new Singleton();
}
return singleton;
}
}
以上是無(wú)法保證他是線程的安全性,優(yōu)化如下:
public class Singleton{
private static Singleton singleton;
private Singleton(){}
public static synchronize Singleton getInstance(){
if (singleton ==null){
singleton = new Singleton();
}
return singleton;
}
}
以上優(yōu)化就確保了線程的安全性,但是性能非常的低效,導(dǎo)致性能下降,那么我們用DCL雙重檢查來(lái)優(yōu)化
public class Singleton{
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if (singleton ==null){
synchronize(Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
以上代碼看起來(lái)很完美吧,那我們來(lái)分析一下:
- 如果檢查第一個(gè)singleton不為null,則不需要執(zhí)行以下代碼,提高了性能
- 如果第一個(gè)singleton為neull,即使有多個(gè)線程同一個(gè)時(shí)間判斷,但是由于synchronize的存在,只會(huì)有一個(gè)線程能夠創(chuàng)建對(duì)象
- 當(dāng)?shù)谝粋€(gè)獲取鎖的線程創(chuàng)建完成后singleton對(duì)象后,其他的第二次判斷singleton一定會(huì)為null,則直接返回已經(jīng)創(chuàng)建好的singleton對(duì)象
是不是很完美的代碼,邏輯上是完全沒(méi)有問(wèn)題的. 但是事實(shí)上并不是,上面的邏輯在jvm指令排序是存在問(wèn)題的.jvm創(chuàng)建實(shí)例化對(duì)象的三個(gè)步驟
memory = allocate(); //1: 分配內(nèi)存空間
ctorInstance(memory); //2: 初始化對(duì)象
instance = memory; //3: 將內(nèi)存空間的地址賦值給對(duì)象的引用
但是由于重排序的原因,步驟2,3可能會(huì)發(fā)生重排序,其過(guò)程如下
memory = allocate(); //1: 分配內(nèi)存空間
instance = memory; //3: 將內(nèi)存空間的地址賦值給對(duì)象的引用
ctorInstance(memory); //2: 初始化對(duì)象
以上的排序就會(huì)導(dǎo)致第二個(gè)判斷會(huì)出錯(cuò) singleton !=null 但是這時(shí)該對(duì)象只是個(gè)內(nèi)存地址,并沒(méi)有初始化實(shí)例,所以return的是singleton的內(nèi)存地址,導(dǎo)致第二個(gè)線程獲取到的是singleton的內(nèi)存地址,引用起來(lái)就會(huì)拋錯(cuò)
此時(shí)就知道錯(cuò)誤在哪里了
singleton = new Singleton();
知道問(wèn)題根源,解決方法有兩個(gè):
- 不允許初始化階段步驟發(fā)生重排序
- 允許重排序,但是不允許其他線程看到這個(gè)排序(即單例的餓漢單例模式)
方案一:
基于以上代碼修改:將變量singleton聲明為volatile
public class Singleton {
// 通過(guò)volatile關(guān)鍵字來(lái)確保安全
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
當(dāng)singleton聲明為volatile后,new實(shí)例化的步驟就不會(huì)重新排序了
方案二:(使用單例模式的餓漢模式)
原理: 利用ClassLoder的機(jī)制,保證初始化instance時(shí)只有一個(gè)線程,jvm在類初始化階段會(huì)獲取一個(gè)鎖,這個(gè)鎖可以同步多個(gè)線程對(duì)一個(gè)類的初始化
public class Singleton {
private static class SingletonHolder{
public static Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.singleton;
}
}
原理圖: