執行緒安全的單例模式常見寫法是雙重檢查加鎖。**如下:
class singleton
public static singleton getinstance()}}
return singleton;
}}
雙重檢查加鎖的單例模式**上就比較複雜,尤其體現在getinstance
方法上,包括兩次檢查singleton
是否是null
,一次加鎖,singleton
用關鍵字volatile
修飾。為什麼寫乙個單例如此複雜呢?
首先是懶漢模式,例項的初始化延遲到getinstance
方法中,為了保證只會生成乙個例項,要先判斷singleton
是否已經初始化,如果已經初始化了,就返回singleton
,沒有的話就建立物件。這就是1的作用。如果是單執行緒的情況,這樣就夠用了。
在多執行緒的情況下,只有1,沒有2,3,就可能導致建立多個例項。例如,執行緒a和執行緒b呼叫getinstance
方法,執行緒a先判斷了1,然後時間片結束了,切換到執行緒b,執行緒b判斷1,然後建立了singleton。時間片有切會執行緒a,執行緒a建立例項。這樣就執行緒a和執行緒b就分別建立了乙個例項了。破壞了單例的結構。
為了解決這個問題,加了synchronized
保證只有乙個執行緒進入臨界區。那只有2,沒有3,可以嗎?還是考慮和前面一模一樣的場景,這次執行緒a和執行緒b都判斷了1了,進入2,執行緒a先進入臨界區,執行緒b發現執行緒a進入了臨界區,就掛在了singleton.class
等等待佇列中,等待執行緒a執行完成。執行緒a繼續執行,建立了乙個singleton
例項。退出了臨界區。然後執行緒b被喚醒,進入臨界區,又建立了乙個singleton
例項。結果又建立了兩個singleton
例項。
所以3的作用很明顯了。在上面例子中,如果執行緒b發現例項已經被建立了(singleton
不等於null
),就直接退出臨界區了。那1和3的作用似乎有點重合了,1似乎就不是必須了。2,3確實就足夠保證單例了。但是加鎖是比較消耗資源的,1就是為了減少資源的消耗。
最後,這麼看來1,2,3,4就足以保證單例了。那為什麼需要加volatile呢?volatile就牽扯到指令重排序的問題了。
要理解為什麼要加volatile,首先要理解new singleton()
做了什麼。new乙個物件有幾個步驟。1.看class物件是否載入,如果沒有就先載入class物件,2.分配記憶體空間,初始化例項,3.呼叫建構函式,4.返回位址給引用。而cpu為了優化程式,可能會進行指令重排序,打亂這3,4這幾個步驟,導致例項記憶體還沒分配,就被使用了。
再用執行緒a和執行緒b舉例。執行緒a執行到new singleton()
,開始初始化例項物件,由於存在指令重排序,這次new操作,先把引用賦值了,還沒有執行建構函式。這時時間片結束了,切換到執行緒b執行,執行緒b呼叫new singleton()
方法,發現引用不等於null
,就直接返回引用位址了,然後執行緒b執行了一些操作,就可能導致執行緒b使用了還沒有被初始化的變數。
加了volatile之後,就保證new
不會被指令重排序。
至此,這就是乙個完整的懶漢模式—>執行緒安全的->雙重檢查加鎖單例模式。
class singleton
private static class lazysomethineholder
public static singleton getinstance()
}
DCL單例模式為什麼還需要加volatile
目錄 dcl是什麼 dcl存在什麼問題 volatile如何解決dcl存在的問題 dcl 即雙重驗證加鎖 什麼是雙重驗證加鎖,看下面 public class person public static person getinstance return person 不難看出,就是在單例模式下獲取例...
單例模式的好處和缺點?為什麼要用單例模式?
單例模式是一種常用的軟體設計模式。在它的核心結構中只包含乙個被稱為單例類的特殊類。通過單例模式可以保證系統中乙個類只有乙個例項而且該例項易於外界訪問,從而方便對例項個數的控制並節約系統資源。如果希望在系統中某個類的物件只能存在乙個,單例模式是最好的解決方案。採用單例模式動機 原因 對於系統中的某些類...
單例模式為什麼要用Volatile關鍵字
執行緒安全的單例模式常見寫法是雙重檢查加鎖。如下 class singleton public static singleton getinstance return singleton 雙重檢查加鎖的單例模式 上就比較複雜,尤其體現在getinstance方法上,包括兩次檢查singleton是否...