1. 程式人生 > >摘抄自今日頭條上的單例模式講解

摘抄自今日頭條上的單例模式講解

1.什麼是單利模式?

簡單說來,就是一個類只能構建一個物件的設計模式

2.單利的模式的程式碼實現第一版

 public class Singleton{     private Singleton(){} //私有建構函式     private static Singleton instance = null;//單例物件     public static Singleton getInstance(){         if(instance == null){             instance = new Singleton;         }         return instance;     } }

為什麼這樣寫呢,我們來解釋幾個關鍵點:

(1).想讓一個類只能構造一個物件,自然不能讓它隨便去做new的操作,因此Singleton的構造方法是私有的。

(2).instance 是Singleton類的靜態成員,也是我們的單例物件,它的初始值可以寫成Null,也可以寫成new Singleton至於其中區別後來會做解視

(3).getInstance是獲取單例物件的方法

如果單例初始值是Null,還未構建,則構建單例物件並返回,這種寫法屬於單例寫法中的懶漢式單例模式

如果單例物件一開始就被new Singleton()主動構建,則不需要判空操作。這種寫法屬於餓漢式單例模式

3.第2點中懶漢式單例的程式碼並非執行緒安全,怎麼修改程式碼,實現一個執行緒安全的單例模式呢?

(1).首先,說下為什麼第2點中的程式碼不是執行緒安全的呢?

假設,Singleton類剛剛被初始化,instance物件還是空,這個時候,兩個執行緒同時訪問getInstance方法,

因為instance 是空,所以兩個執行緒同時通過了條件判斷,開始執行new操作,這樣一來,顯然instance被構建了兩次,讓我們對程式碼做一下修改:

(2).單例模式第二版

public class Singleton{     private Singleton(){} //私有建構函式     private static Singleton instance = null;//單例物件     public static Singleton getInstance(){         if(instance == null){  //雙重檢測機制             synchronized(Singleton.class){//  同步鎖                 if(instance == null){   //雙重檢測機制                     instance = new Singleton();                 }             }         }         return instance;     } }

為什麼這麼寫呢?

a.為了防止new Singleton被執行多次,因此,在new操作之前,加上Synchronized同步鎖,鎖住整個類(注意,這裡不能使用物件鎖)。

b.進入Synchronized臨界區之後,還要做一次判空,因為當兩個執行緒同時訪問的時候,執行緒A構建完對物件,執行緒B也已經通了最初的判空驗證,不做第二次判空的話,執行緒B還是還是會再次構建instance物件。像這樣兩次判空的機制叫做雙重檢測機制

4.以上並非完全安全,且聽我慢慢道來

假設這樣一個場景,當兩個執行緒一先一後訪問getInstance方法的時候,當A執行緒正在構建物件,B執行緒剛剛進入方法:

這種情況表面看似沒什麼問題,要麼Instance還沒被執行緒A構建,執行緒B執行if(instance == null) 的時候得到true,要麼Instance已經被執行緒A構建完成,執行緒B執行if(instance == null) 的時候得到false。真的是這樣嗎?答案是否定的,這裡涉及到了JVM編譯器的指令重排。指令重排是什麼意思?比如java簡單的一句instance  = new Singleton,會被編譯器編譯成如下JVM指令:

memeory =allocate();//1:分配物件的記憶體空間

ctorInstance(memory);//2.初始化物件

instance  = memory;//3:設定instance指向剛分配的記憶體地址

但是這些指令的順序並非一成不變,有時候順序可能程式設計1,3,2,

當執行緒A執行完1,3時,instance物件還未完成初始化,但已經不再指向null,此時如果執行緒B搶佔到CPU資源,執行if(instance == null) 的時候得到的結果會是false,從而返回一個沒有初始化完成的instance物件。

如何避免這個情況呢?我們需要在物件前面增加一個修飾符volatile。

(3).單例模式第三版

 public class Singleton{     private Singleton(){} //私有建構函式     private volatile Singleton instance = null;//單例物件     public static Singleton getInstance(){         if(instance == null){  //雙重檢測機制             synchronized(Singleton.class){//  同步鎖                 if(instance == null){   //雙重檢測機制                     instance = new Singleton();                 }             }         }         return instance;     } }

這裡,對這個volatile簡單解釋下,volatile修飾符阻止了變數訪問前後的指令重排,保證了指令執行順序