1. 一般的單例模式如下:
class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); // 創建實例 } return singleton; } }
問題:構造器私有使得外界無法通過構造器實例化Singleton類,要取得實例只能通過getInstance()方法。這是一個延遲加載的版本,即在需要對象的時候才進行實例化操作。該方法在單線程下能夠正常運行,但是在多線程環境下會出現由於沒有同步措施而導致產生多個單例對象的情況。
原因:在於可能同時有兩個線程A和B同時執行到 if 條件判斷語句,A判斷singleton為空准備執行(//創建實例) 時讓出了CPU時間片,B也判斷singleton為空,接着執行 (//創建實例),此時創建了一個實例對象;A獲取了CPU時間片后接着執行(//創建實例),也創建了實例對象,這就導致多個單例對象的情況
2. 為了解決 1 中出現的問題就是使用synchronized關鍵字,代碼如下:
class Singleton{ private static Singleton singleton; private Singleton(){} public static synchronized Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); //創建實例 } return singleton; } }
這樣解決了多線程並發的問題,但是卻帶來了效率問題:我們的目的是只創建一個實例,即(//創建實例)處代碼只會執行一次,也正是這個地方才需要同步,后面創建了實例之后,singleton非空就會直接返回對象引用,而不用每次都在同步代碼塊中進行非空驗證。
3. 為了解決 2 中出現的問題只對(//創建實例)處進行同步:
class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized(Singleton.class){ singleton = new Singleton(); //創建實例 } } return singleton; } }
這樣會帶來與第一種一樣的問題: 即多個線程同時執行到條件判斷語句時,會創建多個實例。
原因: 問題在於當一個線程創建一個實例之后,singleton就不再為空了,但是后續的線程並沒有做第二次非空檢查
4. 為了解決 3 中出現的問題就是在后續的線程做第二次非空檢查,代碼如下:
class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized(Singleton.class){ if(singleton == null) singleton = new Singleton(); //創建實例 } } return singleton; } }
到這里已經很完美了,看起來沒有問題。No!!!因為這種雙重檢測機制在JDK1.5之前是有問題的,問題還是出在(//創建實例),由所謂的無序寫入造成的。
一般來講,當初始化一個對象的時候,會經歷
a. 內存分配
b. 初始化
c. 返回對象引用
這種方式產生的對象是一個完整的對象,可以正常使用。但是JAVA的無序寫入可能會造成順序的顛倒,即
a. 內存分配、
b. 返回對象引用
c. 初始化的順序,
這種情況下對應到(//創建實例)就是singleton已經不是null,而是指向了堆上的一個對象,但是該對象卻還沒有完成初始化動作。當后續的線程發現singleton不是null而直接使用的時候,就會出現意料之外的問題。
解決方案: JDK1.5之后,可以使用volatile關鍵字修飾變量來解決無序寫入產生的問題,因為volatile關鍵字的一個重要作用是禁止指令重排序,即保證不會出現內存分配、返回對象引用、初始化這樣的順序,從而使得雙重檢測真正發揮作用
5. 最后的雙重檢測單例模式為:
public class Singleton{ private volatile static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }