Double-checked locking真的有效嗎?
作者: zsxwing 日期: 2011-04-29 10:48:06
在很多設(shè)計(jì)模式的書籍中,我們都可以看到類似下面的單例模式的實(shí)現(xiàn)代碼,一般稱為Double-checked locking(DCL)
01 | public class Singleton { |
03 | private static Singleton instance; |
09 | public static Singleton getInstance() { |
10 | if (instance == null ) { //1 |
11 | synchronized (Singleton. class ) { //2 |
12 | if (instance == null ) { //3 |
13 | instance = new Singleton(); //4 |
這樣子的代碼看起來很完美,可以解決instance的延遲初始化。只是,事實(shí)往往不是如此。
問題在于instance = new Singleton();這行代碼。
在我們看來,這行代碼的意義大概是下面這樣子的
mem = allocate(); //收集內(nèi)存
ctorSingleton(mem); //調(diào)用構(gòu)造函數(shù)
instance = mem; //把地址傳給instance
這行代碼在Java虛擬機(jī)(JVM)看來,卻可能是下面的三個(gè)步驟(亂序執(zhí)行的機(jī)制):
mem = allocate(); //收集內(nèi)存
instance = mem; //把地址傳給instance
ctorSingleton(instance); //調(diào)用構(gòu)造函數(shù)
下面我們來假設(shè)一個(gè)場景。
- 線程A調(diào)用getInstance函數(shù)并且執(zhí)行到//4。但是線程A只執(zhí)行到賦值語句,還沒有調(diào)用構(gòu)造函數(shù)。此時(shí),instance已經(jīng)不是null了,但是對象還沒有初始化。
- 很不幸線程A這時(shí)正好被掛起。
- 線程B獲得執(zhí)行的權(quán)力,然后也開始調(diào)用getInstance。線程B在//1發(fā)現(xiàn)instance已經(jīng)不是null了,于是就返回對象了,但是這個(gè)對象還沒有初始化,于是對這個(gè)對象進(jìn)行操作就出錯(cuò)了。
問題就出在instance被提前初始化了。
解決方案一,不使用延遲加載:
01 | public class Singleton { |
03 | private static Singleton instance = new Singleton(); |
09 | public static Singleton getInstance() { |
JVM內(nèi)部的機(jī)制能夠保證當(dāng)一個(gè)類被加載的時(shí)候,這個(gè)類的加載過程是線程互斥的。這樣當(dāng)我們第一次調(diào)用getInstance的時(shí)候,JVM能夠幫我們保證instance只被創(chuàng)建一次,并且會保證把賦值給instance的內(nèi)存初始化完畢。
解決方案二,利用一個(gè)內(nèi)部類來實(shí)現(xiàn)延遲加載:
01 | public class Singleton { |
07 | private static class SingletonContainer { |
08 | private static Singleton instance = new Singleton(); |
11 | public static Singleton getInstance() { |
12 | return SingletonContainer.instance; |
這兩種方案都是利用了JVM的類加載機(jī)制的互斥。
方案二的延遲加載實(shí)現(xiàn)是因?yàn)椋挥性诘谝淮握{(diào)用Singleton.getInstance()函數(shù)時(shí),JVM才會去加載SingletonContainer,并且初始化instance。
不只Java存在這個(gè)問題,C/C++由于CPU的亂序執(zhí)行機(jī)制,也同樣存在這樣的問題。
抱歉,我之前的理解有誤,DCL在Java中失效的原因是JIT比較激進(jìn)的優(yōu)化導(dǎo)致的,在C/C++并不會由于CPU的亂序執(zhí)行(調(diào)用構(gòu)造函數(shù)和賦值這兩個(gè)操作對CPU來說絕對不會亂序的)產(chǎn)生這個(gè)問題。
暫時(shí)不知道Java對于這個(gè)問題是否修復(fù)了。