1. 程式人生 > >第三夢 單例模式

第三夢 單例模式

volatil 內存空間 參考 jdk 開始 並且 其中 nsa 獲取

初識單例

單例模式,算是我們代碼中經常遇見的設計模式之一了。當然我們也上手很快,但是其中的坑也不少,不好好研究一下,這些坑還真不好跳過去。單例簡單分分別為懶漢模式、餓漢模式,那我們就從懶漢模式開始吧。

懶漢模式(線程非安全)

這裏定義一個私有的全局變量singletonPattern,然後通過一個公有的靜態方法對singletonPattern進行判空,如果為空,就new一個類對象出來,然後返回該對象。該種方式可以實現類對象在使用的時候才創建,也就是延時加載。

 1 public class SingletonPattern {
 2 
 3     private static SingletonPattern singletonPattern = null
; 4 5 private SingletonPattern() { 6 } 7 8 public static SingletonPattern getInstance(){ 9 // if這裏存在競態條件 10 if(singletonPattern == null){ 11 singletonPattern = new SingletonPattern(); 12 } 13 return singletonPattern; 14 } 15 }

懶漢模式(線程安全、低效)

一種比較簡單的方式,是同步獲取實例化的方法getInstance(),也就是加上synchronized關鍵字。當然這種方式是非常低效的(jdk後面的版本對synchronized關鍵字段的底層代碼做了很強的優化,所以也不是不可以考慮),具體如下:

 1 public class SingletonPattern {
 2 
 3     private static SingletonPattern singletonPattern = null;
 4 
 5     private SingletonPattern() {
 6     }
 7 
 8     public static
synchronized SingletonPattern getInstance(){ 9 if(singletonPattern == null){ 10 singletonPattern = new SingletonPattern(); 11 } 12 return singletonPattern; 13 } 14 }

懶漢模式(線程安全、高效)

1、雙重鎖校驗DCL(半成品,問題代碼,面試考點),註意看下面羅列的四步。

 1 public class SingletonPatternDcl {
 2 
 3   private static SingletonPatternDcl singletonPatternDcl = null;
 4 
 5   private SingletonPatternDcl() {
 6   }
 7 
 8   public static SingletonPatternDcl getInstance(){
 9     if(singletonPatternDcl == null){                         //1、在實例化的情況下,不需要執行加鎖動作,性能提高
10       synchronized (SingletonPatternDcl.class){              //2、對類上鎖,多個線程的情況下,只有一個線程能夠創建對象
11         if(singletonPatternDcl == null){                     //3、實例化對象為空的情況下創建對象
12           singletonPatternDcl = new SingletonPatternDcl();   //4、創建對象
13         }
14       }
15     }
16     return singletonPatternDcl;
17   }
18 }

2、完美的DCL。上面的DCl看起來是非常完美的,所有的邏輯都考慮到了,但是上面的第四步singletonPatternDcl = new SingletonPatternDcl()創建對象的過程其實並非是一個原子操作,這就導致了問題的產生。我們來分析一下第四步在JVM中具體做了哪些事情:

  • a、給singletonPatternDcl分配內存空間
  • b、調用SingletonPatternDcl的構造函數來初始化該成員變量
  • c、將singletonPatternDcl對象指向a步驟分配的內存空間(這一步執行完之後,singletonPatternDcl就為非null了)

而在JVM的即時編譯器中存在指令重排序的優化,如果c步驟在b步驟之前執行的話:b執行了,singletonPatternDcl不為空了,第二個線程來了,發現singletonPatternDcl已經不為null了,然後直接返回。但是其實這個時候singletonPatternDcl只是一個內存地址,根本還沒有初始化,程序就理所當然的報錯了。解決的方法很簡單,基於volatile解決方案,如下所示:

private static volatile SingletonPatternDcl singletonPatternDcl = null;

volatile的特性禁止指令重排序,保證了上述a、b、c一定會按著abc的順序執行,也就避免了上述產生問題的場景。

餓漢模式(天然的線程安全)

利用類加載的機制,我們可以在類一開始加載的時候就初始化一個實例對象。缺點是無法實現懶加載,並且在某些需要使用動態參數的情況下無法使用。

 1 public class SingletonPatternSafe {
 2 
 3   private static SingletonPatternSafe singletonPatternSafe = new SingletonPatternSafe();
 4 
 5   private SingletonPatternSafe() {
 6   }
 7 
 8   public SingletonPatternSafe getInstance() {
 9     return singletonPatternSafe;
10   }
11 }

這裏加上final也是可以的

private static final SingletonPatternSafe singletonPatternSafe = new SingletonPatternSafe();

靜態內部類(天然的線程安全)

這種方式的單例實現,也是基於JVM本身機制保證了線程安全。其內部類Holder只有getInstance()方法可以訪問。讀取的實例的時候也不需要進行同步,沒有性能的損失。

 1 public class SingletonPatternHolder {
 2 
 3   private static class Holder {
 4     private static final SingletonPatternHolder INSTANCE = new SingletonPatternHolder();
 5   }
 6 
 7   private SingletonPatternHolder() {
 8   }
 9 
10   public static SingletonPatternHolder getInstance(){
11     return Holder.INSTANCE;     //懶漢式的,只有訪問getInstance()方法的時候才實例化
12   }
13 
14 }

枚舉方式(絕對的線程安全)

枚舉實現單例模式有三個特性:自由序列化、線程安全、保證單例。

  • enum的實現是通過繼承了Enum類來實現的,enum結構不能作為子類來繼承其他類,但是可以用來實現接口類;
  • 由於enum內部的實現方式其實是final類型的,所以enum類不可以被繼承;
  • enum有且僅有private構造器,防止外部的額外構造,這恰好和單例模式相符合;
  • 其內部也是枚舉量未被初始化,之後會在靜態代碼中進行初始化,這就非常類似餓漢模式;
  • 對於序列化和反序列化,因為每一個枚舉類型和枚舉變量在JVM中都是唯一的,所以Java在序列化和反序列化枚舉時做了特殊規定,枚舉的writeObject、readObject、readReplace和readResolve等方式是被編譯器禁止的,因此不存在實現序列化接口之後調用readObject會重新創建的心得對象從而破壞單例的問題。

基於上述描述,我們發現enum的方式來構造單例模式,代碼實現起來非常的簡單、自由序列化。並且也是線程的安全,相比起來應該更優選擇

1 public enum  SingletonPatternEnum {
2 
3   /**
4    * 實例化對象
5    */
6   INATANCE
7 }

代碼實例

我的代碼放在GitHub,小夥伴可以作為一個參考、

參考博文

  • Java枚舉enum以及應用:枚舉實現單例模式
  • 如何正確地寫出單例模式
  • 死磕Java並發:Java內存模型之從JMM角度分析DCL
SingletonPattern、我們平常用到的一個設計模式,有必要深入學習,掌握精髓,在實戰中靈活運用。感謝前輩們的分享做為引路人。-------書山有路、人兒需行<<

第三夢 單例模式