設計模式之一:單例模式
設計模式之一:單例模式
目錄介紹
1.單例模式介紹
2.單例模式定義
3.單例模式使用場景
4.單例模式的實現方式
- 4.1 懶漢式【線程不安全】
- 4.2 懶漢式【****synchronized 線程安全】
- 4.3 餓漢式【線程安全】
- 4.4 DCL雙重校驗模式【線程安全】
- 4.5 靜態內部類單例模式【線程安全】
- 4.6 枚舉單例【線程安全】
- 4.7 使用容器實現單例模式
5.Android源碼中單例
- 5.1 InputMethodManager中使用單例模式
- 5.2 LayoutInflater使用的單例模式
- 5.2 通過Context獲取係統級服務的單例模式
6.單例模式總結
7.其他
0.本人寫的綜合案例
案例
說明及截圖
模塊:新聞,音樂,視頻,圖片,唐詩宋詞,快遞,天氣,記事本,閱讀器等等
接口:七牛,阿裏雲,天行,幹貨集中營,極速數據,追書神器等等
1.單例模式介紹
- 單例模式是應用最廣的模式,也是我最先知道的一種設計模式,在深入了解單例模式之前,每當遇到如:getInstance()這樣的創建實例的代碼時,我都會把它當做一種單例模式的實現。
- 單例模式特點
- 構造函數不對外開放,一般為private
- 通過一個靜態方法或者枚舉返回單例類對象
- 確保單例類的對象有且隻有一個,尤其是在多線程的環境下
- 確保單例類對象在反序列化時不會重新構造對象
2.單例模式定義
- 保證一個類僅有一個實例,並提供一個訪問它的全局訪問點
3.單例模式使用場景
- 應用中某個實例對象需要頻繁的被訪問。
- 應用中每次啟動隻會存在一個實例。如賬號係統,數據庫係統。
4.單例模式的實現方式
- 4.1 懶漢式【線程不安全】
- 懶漢式代碼
//懶漢式單例類.在第一次調用的時候實例化自己 public class Singleton { //私有的構造函數 private Singleton() {} //私有的靜態變量 private static Singleton single=null; //暴露的公有靜態方法 public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
- 代碼分析
懶漢式(線程不安全)的單例模式分為三個部分:私有的構造方法,私有的全局靜態變量,公有的靜態方法。 起到重要作用的是靜態修飾符static關鍵字,我們知道在程序中,任何變量或者代碼都是在編譯時由係統自動分配內存來存儲的,而所謂靜態就是指在編譯後所分配的內存會一直存在,直到程序退出內存才會釋放這個空間,因此也就保證了單例類的實例一旦創建,便不會被係統回收,除非手動設置為null。
- 優缺點
優點:延遲加載(需要的時候才去加載) 缺點: 線程不安全,在多線程中很容易出現不同步的情況,如在數據庫對象進行的頻繁讀寫操作時。
- 4.2 懶漢式【**synchronized 線程安全】**
- 懶漢式代碼
public class Singleton { //私有的靜態變量 private static Singleton instance; //私有的構造方法 private Singleton (){}; //公有的同步靜態方法 public static **synchronized** Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
- 代碼分析
這種單例實現方式的getInstance()方法中添加了synchronized 關鍵字,也就是告訴Java(JVM)getInstance是一個同步方法。 同步的意思是當兩個並發線程訪問同一個類中的這個synchronized同步方法時, 一個時間內隻能有一個線程得到執行,另一個線程必須等待當前線程執行完才能執行,因此同步方法使得線程安全,保證了單例隻有唯一個實例。
- 優缺點
優點:解決了線程不安全的問題。 缺點:效率有點低,每次調用實例都要判斷同步鎖 它的缺點在於每次調用getInstance()都進行同步,造成了不必要的同步開銷。這種模式一般不建議使用。
- 4.3 餓漢式【線程安全】
- 餓漢式代碼
//餓漢式單例類.在類初始化時,已經自行實例化 public class Singleton { //static修飾的靜態變量在內存中一旦創建,便永久存在 private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
- 代碼分析
餓漢式在類創建的同時就已經創建好一個靜態的對象供係統使用,以後不再改變,所以天生是線程安全的。其中instance=new Singleton()可以寫成: static { instance = new Singleton(); }
- 4.4 DCL雙重校驗模式
- DCL雙重校驗模式代碼
public class Singleton { private static Singleton singleton; //靜態變量 private Singleton (){} //私有構造函數 public static Singleton getInstance() { if (singleton == null) { //第一層校驗 synchronized (Singleton.class) { if (singleton == null) { //第二層校驗 singleton = new Singleton(); } } } return singleton; } }
- 代碼分析
這種模式的亮點在於getInstance()方法上,其中對singleton 進行了兩次判斷是否空,第一層判斷是為了避免不必要的同步,第二層的判斷是為了在null的情況下才創建實例。
- 優缺點
優點:在並發量不多,安全性不高的情況下或許能很完美運行單例模式 缺點:不同平台編譯過程中可能會存在嚴重安全隱患。
- 模擬分析
假設線程A執行到了singleton = new Singleton(); 語句,這裏看起來是一句代碼,但是它並不是一個原子操作,這句代碼最終會被編譯成多條匯編指令,它大致會做三件事情 (a)給Singleton的實例分配內存 (b)調用Singleton()的構造函數,初始化成員字段 (c)將singleton對象指向分配的內存空間(即singleton不為空了) 但是由於Java編譯器允許處理器亂序執行,以及在jdk1.5之前,JMM(Java Memory Model:java內存模型)中Cache、寄存器、到主內存的回寫順序規定,上麵的步驟b 步驟c的執行順序是不保證了。也就是說執行順序可能是a-b-c,也可能是a-c-b,如果是後者的指向順序,並且恰恰在c執行完畢,b尚未執行時,被切換到線程B中,這時候因為singleton在線程A中執行了步驟c了,已經非空了,所以,線程B直接就取走了singleton,再使用時就會出錯。這就是DCL失效問題。 但是在JDK1.5之後,官方給出了volatile關鍵字,將singleton定義的代碼改成: private volatile static Singleton singleton; //使用volatile 關鍵字
- 4.5 靜態內部類單例模式
- 靜態內部類單例模式
public class Singleton { private Singleton (){} ;//私有的構造函數 public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } //定義的靜態內部類 private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); //創建實例的地方 } }
- 優缺點
優點:延遲加載,線程安全(java中class加載時互斥的),也減少了內存消耗
- 代碼分析
當第一次加載Singleton 類的時候並不會初始化INSTANCE ,隻有第一次調用Singleton 的getInstance()方法時才會導致INSTANCE 被初始化。因此,第一次調用getInstance()方法會導致虛擬機加載SingletonHolder 類,這種方式不僅能夠確保單例對象的唯一性,同時也延遲了單例的實例化。
- 4.6 枚舉單例
-
枚舉單例代碼
public enum Singleton { //enum枚舉類 INSTANCE; public void whateverMethod() { } }
-
代碼分析
枚舉單例模式最大的優點就是寫法簡單,枚舉在java中與普通的類是一樣的,不僅能夠有字段,還能夠有自己的方法,最重要的是默認枚舉實例是線程安全的,並且在任何情況下,它都是一個單例。即使是在反序列化的過程,枚舉單例也不會重新生成新的實例。而其他幾種方式,必須加入如下方法:才能保證反序列化時不會生成新的對象。 private Object readResolve() throws ObjectStreamException{ return INSTANCE; }
4.7 使用容器實現單例模式
-
代碼
public class SingletonManager { private static Map<String, Object> objMap = new HashMap<String,Object>();//使用HashMap作為緩存容器 private Singleton() { } public static void registerService(String key, Object instance) { if (!objMap.containsKey(key) ) { objMap.put(key, instance) ;//第一次是存入Map } } public static ObjectgetService(String key) { return objMap.get(key) ;//返回與key相對應的對象 } }
-
代碼分析
在程序的初始,將多種單例模式注入到一個統一的管理類中,在使用時根據key獲取對應類型的對象。
5.Android源碼中單例
5.1 InputMethodManager中使用單例模式
5.2 LayoutInflater使用的單例模式
5.2 通過Context獲取係統級服務的單例模式
-
6.單例模式總結
- 總結:不管以哪種形式實現單例模式,它們的核心原理是將構造函數私有化,並且通過靜態公有方法獲取一個唯一的實例,在這個獲取的過程中必須保證線程的安全,同時也要防止反序列化導致重新生成實例對象。
- 綜合考慮:推薦使用**4.4 DCL雙重校驗模式,****4.5 靜態內部類單例模式等等**
- 單例對象如果持有Context,那麼很容易引發內存泄漏,此時要注意傳遞給單例對象的Context最好是Application Context
7.其他說明
- 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- 領英:https://www.linkedin.com/in/chong-yang-049216146/
- 簡書:https://www.jianshu.com/u/b7b2c6ed9284
- csdn:https://my.csdn.net/m0_37700275
- 網易博客:https://yangchong211.blog.163.com/
- 新浪博客:https://blog.sina.com.cn/786041010yc
- github:https://github.com/yangchong211
- 喜馬拉雅聽書:https://www.ximalaya.com/zhubo/71989305/
- 脈脈:yc930211
- 360圖書館:https://www.360doc.com/myfiles.aspx
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網上的日子:https://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:yangchong211@163.com
- 阿裏雲博客:https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV
最後更新:2017-10-18 22:34:44