單例模式
什么是單例?
- 該類只能有一個實體,
- 該類負責創建自己的物件,
- 在整個專案中都能訪問到這個實體,
應用場景
- 讀組態檔時,維護全域的Config類,
- 執行緒池、連接池,統一進行分配和管理,
- 網站的計數器,也可以采用單例模式實作,保持同步
代碼實作
餓漢式
中國古代神話中有女媧補天一說,現在天破了,我們去求女媧補天,
女媧用英語來說是 A Goddess In Chinese Mythology,意思就是神話中的女神,女媧是獨一無二的,現在我們就建一個女神類Goddess,
1 public class Goddess {2 3 }神話中,我們都是女媧造出來的,人是不能造女媧的,所以要女媧私有化構造,
1 public class Goddess {2 private Goddess(){};//私有化構造3 }既然人不能女媧,那女媧是怎么來的,女媧伴隨天地初開產生的,所以要自己創建物件,
1 public class Goddess {2 private static final Goddess goddess = new Goddess();//自己創建物件3 private Goddess(){};//私有化構造4 }女媧是神秘的,凡胎肉眼看不到所以要private,static保證了女媧伴隨天地初開,在記憶體中永生,不能被垃圾回收器回收,final保證女媧是不會變的,永遠是那個女神,嘖...嘖...
1 public class Goddess {2 private static final Goddess goddess = new Goddess();//自己創建物件3 private Goddess(){};//私有化構造4 public static Goddess getInstance(){//獲取唯一可用的物件5 return goddess;6 }7 }既然單例不能被實體化,就需要一個靜態的方法來獲取物件,這是單例的“餓漢式”,代碼第二行就產生了女媧,
我們來驗證一下:
1 public class GoddessTest {2 public static void main(String[] args){3 Goddess goddes1 = Goddess.getInstance();4 Goddess goddes2 = Goddess.getInstance();5 System.out.println("兩個物件的參考是否相等:"+(goddes1==goddes2));6 }7 }結果:
兩個物件的參考是否相等:true兩個物件的參考是同一個物件,說明我們實作了單例模式,
懶漢式
1 public class Goddess { 2 private static Goddess goddess ;//此時不創建物件 3 private Goddess(){};//私有化構造 4 public static Goddess getInstance(){//獲取唯一可用的物件 5 6 if (goddess == null) {//如果沒有女媧才去創建 7 goddess = new Goddess(); 8 } 9 return goddess;10 }11 }女媧比較懶,沒事干的時候,處于混沌狀態,第一次求女媧時,女媧才會實體化,
好處:省了一段時間的記憶體,
壞處:第一次請女媧會慢,因為要消耗CPU去實體化,多執行緒下也有問題,如果多個人同時請女媧,會產生很多女媧,不是執行緒安全,
我們加上synchronized進行優化,執行緒呼叫前必須獲取同步鎖,呼叫完后會釋放鎖給其他執行緒用,也就是請女媧必須排隊,大家一個一個來,
1 public class Goddess { 2 private static Goddess goddess ;//此時不創建物件 3 private Goddess(){};//私有化構造 4 public static synchronized Goddess getInstance(){//獲取唯一可用的物件 5 6 if (goddess == null) {//如果沒有女媧才去創建 7 goddess = new Goddess(); 8 } 9 return goddess;10 }11 }然而,這樣多個人都要去搶鎖,即使物件已經實體化,沒有充分利用CPU資源并發優勢(特別是多核情況),
我們把synchronized放到方法體內,如果女媧還沒實體化,才回去搶鎖,這就極大的利用了CPU資源,
代碼如下:
雙檢鎖/雙重校驗鎖
1 //這種方式采用雙鎖機制,安全且在多執行緒情況下能保持高性能, 2 public class Goddess { 3 private volatile static Goddess goddess ;//此時不創建物件 4 private Goddess(){};//私有化構造 5 public static Goddess getInstance(){//獲取唯一可用的物件 6 if (goddess == null) {//如果女媧還沒有實體化,請女媧的人進行等待,準備搶奪請求鎖, 7 synchronized(Goddess.class){ 8 if (goddess == null) {//第一個搶過之后,后面的人也不用等待了,女媧還有五秒到達戰場 9 goddess = new Goddess();10 }11 }12 }13 return goddess;14 }15 }列舉模式
1 public enum Goddess {2 INSTANCE;3 public Goddess getInstance(){4 return INSTANCE;5 }6 }以上方式是在不考慮反射和序列化的情況下實作的,如果考慮了反射,就無法做到單例類只能有一個實體這種說法了,但是列舉單例模式可以避免這兩種情況,
我們以餓漢式為例:
1 public class GoddessTest { 2 public static void main(String[] args) throws Exception { 3 Goddess goddes1=Goddess.getInstance(); 4 Goddess goddes2=Goddess.getInstance(); 5 Constructor<Goddess> constructor=Goddess.class.getDeclaredConstructor(); 6 constructor.setAccessible(true); 7 Goddess goddes3=constructor.newInstance(); 8 System.out.println("正常情況下,兩個物件的參考是否相等:"+(goddes1 == goddes2)); 9 System.out.println("反射攻擊時,兩個物件的參考是否相等:"+(goddes1 == goddes3));10 }11 }結果:
1 正常情況下,兩個物件的參考是否相等:true2 反射攻擊時,兩個物件的參考是否相等:false列舉單例下示例:
1 public class GoddessTest { 2 public static void main(String[] args) throws Exception { 3 Goddess goddes1=Goddess.INSTANCE; 4 Goddess goddes2=Goddess.INSTANCE; 5 Constructor<Goddess> constructor= null; 6 constructor = Goddess.class.getDeclaredConstructor(); 7 constructor.setAccessible(true); 8 Goddess goddes3= null; 9 goddes3 = constructor.newInstance();10 System.out.println("正常情況下,兩個物件的參考是否相等:"+(goddes1 == goddes2));11 System.out.println("反射攻擊時,兩個物件的參考是否相等:"+(goddes1 == goddes3));12 }13 }結果:
1 Exception in thread "main" java.lang.NoSuchMethodException: xxx.xxx.Goddess.<init>()2 at java.lang.Class.getConstructor0(Class.java:3082)3 at java.lang.Class.getDeclaredConstructor(Class.java:2178)4 at com.slw.design.danli.GoddessTest.main(GoddessTest.java:11)反射通過newInstance創建物件時,會檢查該類是否enum修飾,如果是則拋出例外,反射失敗,所以列舉是不怕發射攻擊的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/39961.html
標籤:設計模式
上一篇:設計模式 - 動態代理原理及模仿JDK Proxy 寫一個屬于自己的動態代理
下一篇:通俗易懂設計模式決議——策略模式
