【秒懂设计模式】单例设计模式
?秒懂設計模式——單例設計模式
(三)單例設計模式
1.先解釋一下,什么是單例模式呢?
在Java中是這樣定義的:“一個類有且僅有一個實例,并且自行實例化向整個系統提供。”
顯然從單例模式的定義中,我們可以發現它有三個要點:
①某個類只能有一個實例;
②它必須自行創建這個實例;
③它必須自行向整個系統提供這個實例。
2.要滿足這三個要點,應該如何實現呢?下面讓我們來逐條分析:
①如何保證某個類只能有一個實例?
讓我先來想一下,一個類的對象是如何創建的呢?答案是:一個類的對象的產生是由類構造函數來完成。那么,我們是不是可以通過私有類構造函數來實現呢?
②如何保證類定義中含有一個該類的靜態私有對象?
這個就很簡單了,讓單例類自己創建自己的唯一實例。
③如何保證它自行向整個系統提供這個實例?
這個也不難,對外提供獲取此對象的靜態方法即可。
3.單例模式分為幾種呢?
①懶漢式,線程不安全;
②懶漢式,線程安全;
③餓漢式;
④雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking);
⑤登記式/靜態內部類;
⑥枚舉式;
下面仍然通過一個故事,逐一介紹這幾種類型的單例模式,并且在最后,還會分析一下他們的性能差異。
【講故事】某野雞大學(類),只有一個學生會主席(唯一對象,自行實例化),還是一個漂亮妹子叫“M蓉”,其他附近野鴨大學的學長都喜歡請她去自己宿舍,連夜補習英語(向整個系統提供實例)。
(1)懶漢式,線程不安全
先解釋一下為什么叫懶漢式:“懶”顧名思義就是延遲,懶漢式就是指,只有在獲取此單例對象時,才會去創建此對象。
【Java代碼】
①創建一個單例模式的類。
package com.liyan.lazy; /*** 懶漢式的M蓉(線程不安全)* <p>Title: LazyMRong</p> * @author Liyan * @date 2017年4月27日 下午2:00:44*/ public class LazyMRong {//1.私有空參構造,防止別人創建private LazyMRong(){}//2.自己創建自己的唯一實例private static LazyMRong lazyMRong ;//3.對外提供獲取此對象的靜態方法public static LazyMRong getLazyMRong() {if (lazyMRong == null) {//懶漢式意味著,只有你在想獲取時,才創建此對象return new LazyMRong();}return lazyMRong;}public void teachEnglish() {System.out.println("連夜補習外語!");} }
②創建外部訪問
package com.liyan.lazy; /*** S喆請M蓉補習外語* <p>Title: SZhe</p> * @author Liyan * @date 2017年4月27日 下午2:15:23*/ public class SZhe {public static void main(String[] args) {//通過靜態方法,獲取到單例對象LazyMRong lazyMRong = LazyMRong.getLazyMRong();//調用補習外語方法lazyMRong.teachEnglish();} }
分析:首先懶漢式肯定是延遲初始化的,但是不支持多線程。因為沒有加鎖synchronized,所以有些資料上認為,從嚴格意義上講,它并不算單例模式。
(2)懶漢式,線程安全
這個跟上面的懶漢式,線程不安全,唯一的區別就是:在獲取單例類的靜態方法上,加上了synchronized關鍵字進行修飾;
package com.liyan.lazy; /*** 懶漢式的M蓉(線程安全)* <p>Title: LazyMRong</p> * @author Liyan * @date 2017年4月27日 下午2:00:44*/ public class LazyMRong {//1.私有空參構造,防止別人創建private LazyMRong(){}//2.聲明自己的唯一實例,先不實例化private static LazyMRong lazyMRong ;//3.對外提供獲取此對象的靜態方法public synchronized static LazyMRong getLazyMRong() {if (lazyMRong == null) {//懶漢式意味著,只有你在想獲取時,才創建此對象return new LazyMRong();}return lazyMRong;}public void teachEnglish() {System.out.println("連夜補習外語!");} }
(3)餓漢式分析:毋庸置疑,它是延遲初始化的,能夠在多線程中很好的工作,同時在第一次調用時,才進行初始化,避免了內存的浪費。但是加了synchronized 鎖機制,所以效率很低。
先解釋一下為什么叫餓漢式:“餓”意味著“著急”,餓漢式就是指,不管單例對象有沒有外部訪問,先實例化再說。
【Java代碼】餓漢式的M蓉
package com.liyan.hungry; /*** 餓漢式的M蓉(線程安全)* <p>Title: HungryMRong</p> * @author Liyan * @date 2017年4月27日 下午3:02:18*/ public class HungryMRong {//1.私有空參構造,防止別人創建private HungryMRong(){}//2.創建自己的唯一對象,并直接實例化(餓漢式,因為餓著急,不管三七二十一,直接實例化)private static HungryMRong hungryMRong = new HungryMRong();//3.對外提供獲取此對象的靜態方法public static HungryMRong getHungryMRong() {return hungryMRong;} }
(4)雙重檢查鎖(DCL,即 double-checked locking)分析:顯然餓漢式,不懶,所以沒有延遲初始化,同時它基于 classloder 機制,而沒用加鎖的方式,所以既避免了多線程的同步問題,又使得執行效率得到提高。但是,因為它在類加載時就初始化,所以會造成內存的浪費。
先解釋一下為什么叫雙重檢查鎖:顧名思義,會涉及到兩把鎖:
①進入方法過后,先檢查實例是否存在,如果不存在才進入下面的同步塊;
②進入同步塊后,再次檢查實例是否存在,如果不存在,就在同步的情況下創建該實例。
【Java代碼】雙重檢查鎖的M蓉
package com.liyan.dcl; /*** DCL雙重檢查鎖的單例模式的M蓉* <p>Title: DCLMrong</p> * @author Liyan * @date 2017年4月27日 下午4:54:30*/ public class DCLMrong {//1.私有空參構造,防止別人創建private DCLMrong (){}//2.聲明唯一的單例對象;注意這里用到了volatile關鍵字!private volatile static DCLMrong dclMrong ;//3.對外提供獲取此對象的靜態方法public static CLMrong getDclMrong() {//第一把鎖:先檢查實例是否存在,如果不存在才進入下面的同步塊;if (dclMrong == null) {synchronized (DCLMrong.class) {//第二把鎖:再次檢查實例是否存在,如果不存在,就在同步的情況下創建一個實例。if (dclMrong == null) {return new DCLMrong();}}}return dclMrong;} }
分析:這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。說明:?雙重檢查加鎖機制的實現會使用一個關鍵字volatile,它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存,從而確保多個線程能正確的處理該變量。
(5)登記式/靜態內部類
先解釋一下為什么叫登記式:見名知意,想要獲取某單例對象,沒登記的必須先登記,然后才能獲取。此時我們還需要一個登記簿(Map集合),當然,這個登記簿可以記錄一堆單例對象,而不僅僅是一個。
這個模式確實復雜了一些,而且很多網上的例子也是覺得有所不妥,我根據個人理解,寫了如下的代碼,如果各位看官覺得也有疑問,歡迎留言討論。
【Java代碼】
①創建登記式單例模式類
package com.liyan.register; import java.util.HashMap; import java.util.Map; /*** 登記式單例模式* <p>Title: RegisterMrong</p> * @author Liyan * @date 2017年4月27日 下午6:38:22*/ public class RegSingleton {//1.私有空參構造,防止別人創建private RegSingleton(){}//2.創建一個登記簿,在靜態代碼塊中創建自己的唯一對象private static Map<String, RegSingleton> map = new HashMap<String, RegSingleton>(0); //在登記簿上登記private static void singletonHolder(String name) {if(name==null){ name="RegSingleton"; } RegSingleton singleton = new RegSingleton(); //利用反射獲取類名,并作為map集合的keymap.put(name, singleton);System.out.println("已將"+ name +"記錄到登記簿!");}//3.對外提供獲取此對象的靜態方法public static RegSingleton getInstance(String name){ if(name==null){ name="RegSingleton"; System.out.println("名字為空,自動找RegSingleton!");} //查詢登記簿上是否登記過if(map.get(name)==null){ System.out.println("名字"+name+"未在登記簿中登記!");try { //如果沒有登記過,則先進行登記System.out.println("名字"+name+"開始在登記簿中登記!");RegSingleton.singletonHolder(name);//登記之后再返回該對象System.out.println("名字"+name+"已經登記完成!");return map.get(name);} catch (Exception e) { e.printStackTrace(); } }else {//如果登記過,直接返回該單實例System.out.println("名字"+name+"之前在登記簿中登記過了!");return map.get(name); }return null;} public void teachEnglish() {System.out.println("連夜補習外語!");} }
②創建外部訪問
package com.liyan.register; /*** S喆請M蓉補習外語* <p>Title: SZhe</p> * @author Liyan * @date 2017年4月27日 下午2:15:23*/ public class SZhe {public static void main(String[] args) {RegSingleton mrong = RegSingleton.getInstance("Mrong");mrong.teachEnglish();} }
③結果
名字Mrong未在登記簿中登記! 名字Mrong開始在登記簿中登記! 已將Mrong記錄到登記簿! 名字Mrong已經登記完成! 連夜補習外語!
①相同點:都利用ClassLoder機制,來保證初始化時只有一個線程。分析:主要是比較一下,登記模式和雙重檢驗鎖式有何異同?
②不同點:雙重檢驗鎖式在類一被裝載是就被初始化了,所以它沒有延遲的效果;而登記模式,只有再主動調用獲取該對象的靜態方法時,才被初始化,所以它有延遲效果。
(6)枚舉式
先解釋一下為什么叫枚舉式:不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化。不過,由于 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,但是《Effective Java》一書中的話有這樣一段很經典的話:“單元素的枚舉類型已經成為實現Singleton的最佳方法!”
【Java代碼】枚舉單例模式的M蓉
package com.liyan.enummodel; /*** 枚舉單例模式的M蓉* <p>Title: EnumMrong</p> * @author Liyan * @date 2017年4月27日 下午9:23:59*/ public class EnumMrong {//1.私有空參構造,防止別人創建private EnumMrong() {}//2.申明自己的唯一對象public static EnumMrong getInstance() {return Singleton.INSTANCE.getInstance();}//3.對外提供獲取此對象的靜態方法private static enum Singleton {INSTANCE;private EnumMrong singleton;//在構造方法中實例化對象,保證只調用一次private Singleton() {singleton = new EnumMrong();}public EnumMrong getInstance() {return singleton;}} }
??
總結
以上是生活随笔為你收集整理的【秒懂设计模式】单例设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AbstractQueuedSynchr
- 下一篇: Canvas 画占比图 解决canva