併發下的單例模式寫法。
阿新 • • 發佈:2018-12-04
錯誤示例:
public class Singleton { private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ // 1 synchronized (Singleton.class){ // 2 if(singleton == null){ // 3 singleton = new Singleton(); // 4 } } } return singleton; }}
首先我們知道例項化一個物件有3個步驟:
-
分配記憶體空間
-
初始化物件
-
將記憶體空間的地址賦值給對應的引用
因為JVM存在指令重排,有可能2和3進行了重新排序,先執行了2.將記憶體空間的地址賦值給對應的引用。那麼在多執行緒的情況下將導致singleton!=null, 那麼就有可能發生災難性的後果。
如何才能保證2,3的執行順序不發生變化,保證安全呢? 只需要在靜態變數前面加個volatile關鍵字即可。因為volatile關鍵字能夠保證初始化物件優先於將記憶體空間的地址賦值給對應的引用的操作。即保證了操作不發生重排序, 這個保證是依靠jvm對volatile底層的實現-------插入了對應的記憶體屏障來保證不發生重排序。這是其中的一種單例實現方案。
還有第二中可以依靠類載入機制來保證單例模式在多執行緒下的執行。
public class Singleton {
private static class SingletonHolder{
public static Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.singleton;
}}
這種方式實際上是利用了classloder的機制來保證初始化instance時只有一個執行緒。JVM在類初始化階段會獲取一個鎖,這個鎖可以同步多個執行緒對同一個類的初始化。
Java語言規定,對於每一個類或者介面C,都有一個唯一的初始化鎖LC與之相對應。從C到LC的對映,由JVM的具體實現去自由實現。JVM在類初始化階段期間會獲取這個初始化鎖,並且每一個執行緒至少獲取一次鎖來確保這個類已經被初始化過了。