在Java多線程程序中,有時須要採用延遲初始化來下降初始化類和建立對象的開銷,雙重檢查鎖定是常見的延遲初始化技術,但它是一種錯誤的用法安全
public synchronized static Instance getInstance() { if (instance == null) { instance = new Instance(); } return instance; }
多線程狀況下性能開銷比較大。多線程
public static Instance getInstance2() { if (instance2 == null) { synchronized (UnsafeLazyInitialization.class) { if (instance2 == null) { instance2 = new Instance(); } } } return instance2; }
這裏看起來很完美,可是是一個錯誤的優化,代碼在讀取到instance2不爲null的時候,instance引用的對象有可能換沒有完成初始化,這樣返回的instance2是有問題的。
出現這個問題的根源在什麼地方?instance2 = new Instance();
這一行代碼在處理器執行的時候有三部操做:
一、memory = allocate() //分配內存
二、ctorInstance(memory) //初始化對象
三、instance = memory //設置instance指向剛剛分配的內存地址性能
上面的三行代碼中,2和3之間可能會被指令重排序。若是重排序以後的順序爲1,3,2.線程A執行2的時候,線程A判斷instance2不爲空,返回的instance2對象就是一個還未初始化的對象。優化
因此對於上面的解決思路有兩種:
一、不容許2和3進行指令的重排序
三、容許2和3重排序,可是不容許其餘線程看到這個重排序。操作系統
/**聲明爲volatile以後,2和3的指令重排序會被禁止*/ public static volatile Instance instance3; public static Instance getInstance3() { if (instance3 == null) { synchronized (UnsafeLazyInitialization.class) { if (instance3 == null) { instance3 = new Instance(); } } } return instance3; }
-基於類初始化的解決線程
public class InstanceFactory { private static class InstanceHolder { public static Instance instance = new Instance(); } public static Instance getInstance () { return InstanceHolder.instance; } }
這個是基於JVM的特性:JVM在類初始化的時候,會執行類初始化,在執行類初始化期間,JVM會獲取一把鎖,這個鎖能夠同步多個線程對同一個類的初始化。初始化一個類,包括執行這個類的靜態初始化和初始化這個類中的靜態字段。根據Java語言規範,在首次發生下面的任何一種狀況,一個類或接口類型將當即被初始化。
1)T的實例類型被建立
2)T是一個類, 且T中的靜態方法被調用
3)T聲明的一個靜態字段被賦值
4)T聲明的靜態字段被使用code
在這裏,首次執行getInstance(),那麼InstanceHolder會進行初始化。對象
任何的線程安全操做在底層都是對應指令重排序以及內存可見性的問題。操做系統纔是根本啊~~排序