JAVA單例模式實現詳解
單例模式的特點
- 類的內部包括待返回的類的例項,為private static型別
- 類的建構函式為私有建構函式,以防止在其他類中例項化,private的作用是防止在其他類中用建構函式建立該類的例項
- 提供一個獲取例項的靜態方法
單例模式1:飽漢模式+考慮了執行緒安全(雙重檢查鎖)
public class Singleton1 {
private static volatile Singleton1 instance = null;//volatile不具有原子性
//構造方法:如果構造方法是 public,那麼任何類都可以 new 我的物件,不能保證單例,
// 所以單例模式構造方法一定是 private的
private Singleton1() {
}
public static Singleton1 getInstance() {
if (instance == null) {//只有instance == null這種情況下需要進行加鎖synchronized
synchronized (Singleton1.class) {//靜態方法中上鎖用的是類名.class
if (instance == null)
instance = new Singleton1();
}
}
return instance;
}
}
為什麼需要第一重instance == null呢?
這裡就涉及一個性能問題了,因為對於單例模式的話,newSingleTon()只需要執行一次就 OK 了,
而如果沒有第一重instance == null 的話,每一次有執行緒進入getInstance()時,均會執行鎖定操作來實現執行緒同步,
這是非常耗費效能的,而如果我加上第一重instance == null 的話,
那麼就只有在第一次,也就是instance ==null 成立時的情況下執行一次鎖定以實現執行緒同步,
而以後的話,便只要直接返回Singleton例項就 OK 了而根本無需再進入 lock語句塊了,這樣就可以解決由執行緒同步帶來的效能問題了。
為什麼需要第二重instance == null呢?
考慮這樣一種情況,就是有兩個執行緒同時到達,即同時呼叫getInstance() 方法,
此時由於instance == null ,所以很明顯,兩個執行緒都可以通過第一重的 instance == null ,
進入第一重 if語句後,由於存在鎖機制,所以會有一個執行緒進入 lock 語句並進入第二重 instance == null ,
而另外的一個執行緒則會在lock 語句的外面等待。
而當第一個執行緒執行完new SingleTon()語句後,便會退出鎖定區域,此時,第二個執行緒便可以進入lock 語句塊,
此時,如果沒有第二重instance == null 的話,那麼第二個執行緒還是可以呼叫 new SingleTon()語句,
這樣第二個執行緒也會建立一個SingleTon例項,這樣也還是違背了單例模式的初衷的,
所以這裡必須要使用雙重檢查鎖定。
為什麼要用volatile?
主要是為了保證有序性(禁止指令重排序)
java中new一個物件是由多個步驟組成的,而這些步驟可能會被編譯器重排序,從而導致其他執行緒獲取到一個沒有被完全初始化的物件,而 volatile 就
是告訴編譯器不要重排序。
這裡volatile的作用僅僅是阻止指令重排序, 不涉及可見性問題, 可見性已經由synchronized來保證了(synchronized也有阻止重排序的功能, 但是其由monitor來實現, monitorenter和monitorexit之間的指令仍可能被重排序).
單例模式2:餓漢模式
public class Singleton2 {
private static final Singleton2 singleton = new Singleton2();
private Singleton2() {
}
public static Singleton2 getInstance() {
return singleton;
}
}
- 餓漢的好處是天生的執行緒安全(得益於類載入機制),寫起來超級簡單,使用時沒有延遲;
- 壞處是有可能造成資源浪費(如果類載入後就一直不使用單例的話)
單例模式3:Holder模式(增加了一個靜態內部類)
public class Singleton3 {
// 載入一個類時,其內部類不會同時被載入。
// 一個內部類被載入,當且僅當其某個靜態成員(靜態域、構造器、靜態方法等)被呼叫時發生。
private static class SingletonHolder {
private static final Singleton3 singleton = new Singleton3();
private SingletonHolder() {
}
}
private Singleton3() {
}
public static Singleton3 getInstance() {
return SingletonHolder.singleton;
}
}
- 相對於餓漢模式,Holder模式僅增加了一個靜態內部類的成本,
- 與飽漢的執行緒安全寫法的效果相當(略優),都是比較受歡迎的實現方式。同樣建議考慮。
- 該實現方式比較簡單,而且既實現了由前述事實所保證的惰性初始化(Lazy-Initialazation),又由JVM保證了多執行緒併發訪問的正確性。
參考連結:
為什麼要雙重檢查鎖
https://blog.csdn.net/u010365819/article/details/80240255為什麼要用volatile?
https://www.zhihu.com/question/56606703