----<a target="blank">java培訓、Android培訓、iOS培訓、.Net培訓</a>、期待與您交流! -------
本篇博客對單例模式的餓漢式、懶漢式應用在多線程下是否存在安全隱患及其解決方法進行細節講述。
單例模式
定義:確保一個類只有一個實例,而且自行實例化并向整個系統提供這個實例。
類型: 創建類模式
單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易于外界訪
問,從而方便對實例個數的控制并節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。
要點:主要有三個,一是某個類只能有一個實例;二是它必須自行創建這個實例;三是它必須自行向整個系統提供這個實例。
注意:如何保證對象的唯一性?
單例的步驟:
單例模式中的餓漢式(又叫單例設計模式)、懶漢式(又叫延遲加載設計模式)。
1 //餓漢式(開發時常用) 2 class Single{//類一加載,對象就已經存在了 3 PRivate static final Single S= new Single(); 4 private Single();//私有化該類的構造函數,這樣其它類就不能夠再用new方式創建對象了。 5 public static Single getInstance(){ 6 return S;//返回一個本類對象,因為該方法是靜態的,靜態的方法訪問的只能是靜態的S,所以Single S = new Single()必須是靜態的。 7 } 8 } 9 10 class SingleDemo{11 public static void main(String[] args){12 Single ss = Single.getInstance();//因為Single類的構造函數已經private(私有化了),所以此處只能用 類名.函數名 調用getInstance方法。13 //但是類名調用有前提:必須是靜態的方法.所以getInstance是靜態方法,要有static修飾,14 //而該方法中返回一個已經創建好的對象ss,保證只有一個對象.15 }16 }
1 //懶漢式(延遲加載形式)(面試常被問到) 2 class Single{//類一加載,只是先創建引用,只有調用了getInstance方法,才會創建對象 3 private static Single S = null; 4 private Single(); 5 public static Single getInstance(){ 6 if(S==null){ 7 S = new Single(); 8 return S; 9 }10 }11 }12 //如果后期被多線程并發訪問時,保證不了對象的唯一性,存在安全隱患,如果改后,效率降低13 class SingleDemo{14 public static void main(String[] args){15 Single ss = Single.getInstance();16 }17 }
問題1. 分析兩者在多線程并發訪問情況下存在的安全隱患?
說到這里討論多線程并發訪問下存在的安全隱患,就必須要了解一下,線程安全問題產生的原因是什么?
當一個線程在執行操作共享數據的多行代碼過程中,其它線程參與了運算就會導致線程安全問題的產生。
解決思路:就是將多條操作共享數據的代碼封裝起來,當有線程在執行這些代碼的時候,其他線程不可參與運算。必須要當前線程把這些代碼都執行完畢后,其他線程不可參與運算。
?。?)在Java中,用同步代碼塊就可以解決這個問題
同步代碼塊格式: synchronized(對象){
需要被同步的代碼塊
}
同步的前提:同步中必須有多個線程并使用同一把鎖
同步的好處:解決了線程的安全問題
同步的弊端:會相應降低效率,因為線程外的其它線程都會判斷同步鎖。
?。?)同步函數也可以解決問題:將synchronized放到方法中修飾
?。?)同步函數與同步代碼塊的區別?
同步函數使用的鎖是固定的this。同步代碼塊使用的鎖是任意的對象(建議使用)
?。?)靜態的同步函數使用的瑣是:該函數所屬字節碼文件對象,可用當前 類名.class 表示。
對于安全隱患分析:
在餓漢式中(不存在安全隱患),當遇到多線程并發訪問時,getInstance加載到線程的run()方法中,返回的對象S是共享數據,因為操作共享數據的代碼只有一句,所以不會涉及到線程安全問題。
在懶漢式中(存在安全隱患),共享數據是S,且操作共享數據的代碼有多條,當其中一個線程A在執行期間,執行到S=new Single();前(即執行完if(S==null),但還沒有執行S=new Single()時 ),突然被切換執行權,導致下一條線程B開始執行,一直到線程B創建完對象S后,線程A重新獲得執行權,此時線程A又會再次執行S=new Single();再次創建對象,這樣線程A、線程B各自創建了一個對象,導致對象不唯一,也就不能是單例了!
2. 存在的安全隱患如何解決?
(1)首先解決懶漢式中多線程并發訪問時存在的對象不唯一的安全隱患?。丛趃etInstance()方法上用synchronized修飾)
1 //懶漢式(延遲加載形式)(面試常被問到) 2 class Single{//類一加載,只是先創建引用,只有調用了getInstance方法,才會創建對象 3 private static Single S = null; 4 private Single(); 5 public static synchronized Single getInstance(){ 6 if(S==null){ 7 S = new Single(); 8 return S; 9 }10 }11 }12 13 class SingleDemo{14 public static void main(String[] args){15 Single ss = Single.getInstance();16 }17 }
?。?)問題2. 問題又來了,因為使用同步函數,雖然解決了安全隱患,但是同樣的降低了效率。那么是什么原因導致多線程并發訪問的效率降低了呢?
分析:因為第一次創建完對象后,之后的線程每一次獲取對象都要先判斷同步鎖,因此導致效率降低。
問題3. 懶漢式在線程安全前提下如何提高效率?
解決思路:不用同步函數中的同步,改用同步代碼塊中的同步,上代碼
1 //懶漢式(延遲加載形式)(面試常被問到) 2 class Single{//類一加載,只是先創建引用,只有調用了getInstance方法,才會創建對象 3 private static Single S = null; 4 private Single(); 5 public static Single getInstance(){ 6 if(S==null){//此處多加了一次判斷 7 synchronized(Single.class){//使用同步代碼塊,此處注意使用Single.class鎖,因為getInstance()是靜態方法,所以只能用Single.class .而this.getClass()鎖是用于非靜態的方法。 8 if(S==null){ 9 S = new Single();10 }11 }12 return S;13 }14 }15 }16 17 class SingleDemo{18 public static void main(String[] args){19 Single ss = Single.getInstance();20 }21 }
上述代碼中,多加了一次判斷是為了解決效率問題,這樣當線程A創建完對象后,線程B在運行時首先判斷S==null,此時因為線程A已經創建完對象,所以線程B就不在執行創建對象操作。使用同步鎖是為了解決安全問題。(以上都是以程序中只有線程A、線程B來解釋的,再多線程原理也是一樣的)
結論:所以說真正程序開發時,多數用的是餓漢式;懶漢式當多線程并發訪問時,保證不了對象的唯一性(多線程切換執行權執行程序,導致對象不唯一),雖經過修改保證安全,但是其效率相應降低。
新聞熱點
疑難解答