標籤:
單例模式的寫法
1. 懶漢模式(線程不安全)
懶漢模式:顧名思義就是需要使用的時候才執行個體化;
線程不安全:設想兩個線程同時都運行到判斷instance是否等於null的if語句,並且instance的確沒有被建立,那麼這兩個線程都會建立一個執行個體(執行:instance = new Singleton1),此時就不滿足單例模式的要求。
package Singleton;public class Singleton1 { private Singleton1(){}//private僅內建函式可以調用 private static Singleton1 instance = null; public static Singleton1 getInstance(){ if(instance == null){ instance = new Singleton1();//建構函式聲明為private,僅內建函式可以調用,從而確保只建立一個執行個體 } return instance; }
}
2. 懶漢模式(安全執行緒)
安全執行緒:加上同步鎖(synchronized)保證安全執行緒。此時若兩個線程同時建立一個執行個體,由於在一個時刻只有一個線程能獲得同步鎖,當一個線程加上同步鎖時,另一個線程只能等待。當第一個線程發現還沒有建立執行個體時它建立一個執行個體。接著第一個線程釋放同步鎖,此時第二個線程可以加上同步鎖,但發現此時已經建立過一個執行個體,就不會重複建立一個執行個體。從而保證單例模式。
package Singleton;public class Singleton2 { private Singleton2() {}// private僅內建函式可以調用 private static Singleton2 instance; public static synchronized Singleton2 getInstance() {//加同步鎖,解決線程同步問題 if (instance == null) { instance = new Singleton2();// 建構函式聲明為private,僅內建函式可以調用,從而確保只建立一個執行個體 } return instance; }}
3. 懶漢模式(雙重校正鎖)
考慮第2種方法由於加鎖是一個耗時的操作,所以我們想辦法減少加鎖的次數。
本方法:如果執行個體已經被建立,直接返回即可;如果執行個體沒有被建立,此時加鎖保證安全執行緒再建立執行個體。
注意:關鍵字volatile
Volatile的作用: 強制每次都直接讀記憶體,阻止重排序,確保 voltile 類型的值一旦被寫入緩衝必定會被立即更新到主存。
因為:在JAVA多線程環境下,每個Java線程除了共用的虛擬機器棧外和Java堆之外,還存在一個獨立私人的堆記憶體(預設情況下大小為512KB,線上程被建立時分配,可以通過-Xss選項調節其預設值大小)。每個線程獨立運行,彼此之間都不可見,線程的私人堆記憶體中保留了一份主記憶體的拷貝,只有在特定需求的情況下才會與主存做互動(複製/重新整理)。此時就會出現一種情況,雖然在某個線程私人的堆記憶體上已經建立出一個執行個體,由於線程還沒有與主記憶體進行互動,導致主記憶體上還沒有這個執行個體。此時用volatile就可以解決這個問題。
package Singleton;public class Singleton3 { private Singleton3() {}// private僅內建函式可以調用 private volatile static Singleton3 instance = null; public static synchronized Singleton3 getInstance() {//加同步鎖,解決線程同步問題 if (instance == null) {//加鎖耗時,減少加鎖的次數 synchronized (Singleton3.class){ if (instance == null) { instance = new Singleton3();// 建構函式聲明為private,僅內建函式可以調用,從而確保只建立一個執行個體 } } } return instance; }}
4. 餓漢模式
餓漢模式: 相對於懶漢模式,餓漢模式就是在類載入的時候就建立執行個體。
這種方式基於classloder機制避免了多線程的同步問題,instance在類裝載時就執行個體化。下面就是餓漢模式的兩種寫法。
package Singleton;public class Singleton4 { private static Singleton4 instance = new Singleton4(); private Singleton4 (){} public static Singleton4 getInstance() { return instance; } }
package Singleton;public class Singleton5 { private static Singleton5 instance = null; static { instance = new Singleton5(); } private Singleton5 (){} public static Singleton5 getInstance() { return Singleton5.instance; } }
5. 靜態內部類
這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟餓漢模式不同的是:餓漢模式只要Singleton類被裝載了,那麼instance就會被執行個體化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,才會顯示裝載SingletonHolder類,從而執行個體化instance。想象一下,如果執行個體化instance很消耗資源,我想讓他消極式載入,另外一方面,我不希望在Singleton類載入時就執行個體化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被載入,那麼這個時候執行個體化instance顯然是不合適的。這個時候,這種方式相比餓漢模式就顯得很合理。
package Singleton;public class Singleton6 { private static class SingletonHolder { private static final Singleton6 INSTANCE = new Singleton6(); } private Singleton6() {} public static final Singleton6 getInstance() { return SingletonHolder.INSTANCE;//單例模式延遲到SingletonHolder這個類載入 }}
6. 枚舉
枚舉:它不僅能避免多線程同步問題,而且還能防止還原序列化重新建立新的對象。
package Singleton;public enum Singleton7 { INSTANCE; }
參考資料
- 《Think in Java》
- 《Head first design pattern》
- 《劍指offer》
- http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
- http://www.tuicool.com/articles/uyEvaeQ
Java 設計模式---單例模式