這次分享我們就來談談單例模式的使用,其實在本公眾號設計模式的第一篇分享就是單例模式,為什麼又要討論單例模式了?主要是那篇文章談的比較淺,只對單例模式的主要思想做了一個分享,這篇文章會從多個方面去分享單例模式的使用,下麵進入正題。 使用Java做程式的小伙伴都知道單例,尤其是使用spring框架做項目 ...
這次分享我們就來談談單例模式的使用,其實在本公眾號設計模式的第一篇分享就是單例模式,為什麼又要討論單例模式了?主要是那篇文章談的比較淺,只對單例模式的主要思想做了一個分享,這篇文章會從多個方面去分享單例模式的使用,下麵進入正題。
使用Java做程式的小伙伴都知道單例,尤其是使用spring框架做項目的,大家都知道spring框架管理類預設都是單例模式的,並且是線程安全的。那麼如果保證一個類只被初始化一次且線程安全了?帶著這個問題我們開始這篇文章。
在剛接觸設計模式的時候,大多數人首先接觸到的第一個設計模式就是單例模式,當然大多數人都只停留在單例模式,在平時的開發中用的少之又少,這篇文章會幫助大家從多個方面理解單例模式,此文代碼較多,如果對單例模式概念不是很清楚的小伙伴可以看另一篇文章《設計模式之單例模式》。
1. 懶漢模式
package com.study.concurrency.base_concurrency.example.singleton; /** * <p>Title: Singleton01</p > * <p>Description: 懶漢模式 單例實例在第一次使用時進行創建 </p > * <p>Company: http://www.yinjiedu.com</p > * <p>Project: concurrency</p > * * @author: qiwei * @Date: 2019/8/20 22:41 * @Version: 1.0 */ public class Singleton01 { private Singleton01() { } //單例對象 private static Singleton01 instance = null; /** * @description: 靜態的工廠方法 * @auther: qiwei * @date: 2019/8/20 22:43 * @return: Singleton01 */ public static Singleton01 getInstance() { if (instance == null) { instance = new Singleton01(); } return instance; } }
上述代碼在單線程下是沒有問題的,但是在多線程情況下是線程不安全的,出現問題的代碼主要是在下麵這段代碼:
2. 餓漢模式
為瞭解決上面懶漢模式線程不安全問題,我們可以使用餓漢模式創建類實例。餓漢模式是單例實例在類裝載時進行創建,代碼如下:
package com.study.concurrency.base_concurrency.example.singleton; /** * <p>Title: Singleton02</p > * <p>Description: 餓漢模式</p > * <p>Company: http://www.yinjiedu.com</p > * <p>Project: concurrency</p > * * @author: qiwei * @Date: 2019/8/20 22:53 * @Version: 1.0 */ public class Singleton02 { private Singleton02() { } //單例對象 private static Singleton02 instance = new Singleton02(); /** * @description: 靜態的工廠方法 * @auther: qiwei * @date: 2019/8/20 22:55 * @return: Singleton02 */ public static Singleton02 getInstance() { return instance; } }
餓漢模式問題:
餓漢模式雖然是線程安全的,但是在實際開發中,會有一些大家需要註意的問題。
第一點:餓漢模式是在類裝載的時候就創建的,所以如果類的構造方法裡面很多複雜的處理,類裝載就會比較慢,影響程式性能;
第二點:餓漢模式創建的示例,不管在項目其他地方有沒有使用,類的實例已經創建,如果不使用,會造成資源的浪費。
基於以上兩點,大家在使用餓漢模式的時候需要註意實際的使用場景。
3. 懶漢模式之線程安全
我們看到在使用懶漢模式創建實例時是線程不安全的,這裡我們使用同步鎖的方式,讓懶漢模式也是線程安全的,代碼如下:
package com.study.concurrency.base_concurrency.example.singleton; /** * <p>Title: Singleton01</p > * <p>Description: 懶漢模式 </p > * <p>Company: http://www.yinjiedu.com</p > * <p>Project: concurrency</p > * * @author: qiwei * @Date: 2019/8/20 22:41 * @Version: 1.0 */ public class Singleton03 { private Singleton03() { } //單例對象 private static Singleton03 instance = null; /** * @description: 靜態的工廠方法 * @auther: qiwei * @date: 2019/8/20 22:43 * @return: Singleton01 */ public static synchronized Singleton03 getInstance() { if (instance == null) { instance = new Singleton03(); } return instance; } }
這裡我們使用了synchronized關鍵字保證了線程安全,但是在實際開發中不要這樣寫,因為synchronized會帶來比較嚴重的性能開銷。
4. 懶漢模式之雙重同步鎖模式(線程不安全)
使用雙重同步鎖實現單例模式,代碼如下:
package com.study.concurrency.base_concurrency.example.singleton; /** * <p>Title: Singleton01</p > * <p>Description: 懶漢模式 </p > * <p>Company: http://www.yinjiedu.com</p > * <p>Project: concurrency</p > * * @author: qiwei * @Date: 2019/8/20 22:41 * @Version: 1.0 */ public class Singleton04 { private Singleton04() { } //單例對象 private static Singleton04 instance = null; /** * @description: 靜態的工廠方法 * @auther: qiwei * @date: 2019/8/20 22:43 * @return: Singleton01 */ public static Singleton04 getInstance() { if (instance == null) { synchronized (Singleton04.class) { if (instance == null) { instance = new Singleton04(); } } } return instance; } }
上面代碼在synchronized這裡啟用了雙重檢測機制,但是這種寫法在多線程情況下也是線程不安全的,這個原因是JVM和CPU在的指令重排;這裡簡單的介紹一下什麼是電腦指令:
Java實例化一個對象時,主要經歷下麵三步指令:
第一步:分配對象記憶體空間
第二步:初始化對象
第三步:設置instance指向剛分配的記憶體
知道了上面步驟,我們知道一旦在多線程情況下發生了指令重排,就會出現安全問題,例如上面的步驟123變成132 。
5. 懶漢模式之雙重同步鎖模式(線程安全)
使用第四種方式創建單例也是線程不安全的,要想使第四個例子線程安全,我們需要使用到一個關鍵字:volatile。
代碼如下:
package com.study.concurrency.base_concurrency.example.singleton; /** * <p>Title: Singleton01</p > * <p>Description: 懶漢模式 </p > * <p>Company: http://www.yinjiedu.com</p > * <p>Project: concurrency</p > * * @author: qiwei * @Date: 2019/8/20 22:41 * @Version: 1.0 */ public class Singleton05 { private Singleton05() { } //單例對象 private volatile static Singleton05 instance = null; /** * @description: 靜態的工廠方法 * @auther: qiwei * @date: 2019/8/20 22:43 * @return: Singleton01 */ public static Singleton05 getInstance() { if (instance == null) { synchronized (Singleton05.class) { if (instance == null) { instance = new Singleton05(); } } } return instance; } }
volatile可以禁止指令重排,所以上面方式是線程安全的。volatile使用場景主要是:狀態標示量、雙重檢測。
6. 餓漢模式之靜態域實現(錯誤寫法)
餓漢模式第一種實現使用了直接初始化方式,這裡我們可以使用靜態域實現,這也是在平時開發中比較常見的一種方式,但是這裡有一個坑,先埋給大家,代碼如下:
package com.study.concurrency.base_concurrency.example.singleton; /** * <p>Title: Singleton02</p > * <p>Description: 餓漢模式</p > * <p>Company: http://www.yinjiedu.com</p > * <p>Project: concurrency</p > * * @author: qiwei * @Date: 2019/8/20 22:53 * @Version: 1.0 */ public class Singleton06 { private Singleton06() { } static { instance = new Singleton06(); } //單例對象 private static Singleton06 instance = null; /** * @description: 靜態的工廠方法 * @auther: qiwei * @date: 2019/8/20 22:55 * @return: Singleton02 */ public static Singleton06 getInstance() { return instance; } }
上面靜態域單例模式是有問題的,大家想想有什麼問題,在最後我們再討論,大家也可以debug調試找找問題。
7. 單例模式之枚舉創建
在前面集中創建單例的例子中,我們接觸了懶漢模式、餓漢模式。個人認為不管是懶漢模式還是餓漢模式,都不是最好的方案,這裡我推薦使用枚舉類創建單例,既保證了線程安全的需求,又保證了資源不被浪費。代碼實現如下:
package com.study.concurrency.base_concurrency.example.singleton; /** * <p>Title: Singleton01</p > * <p>Description: 枚舉模式 </p > * <p>Company: http://www.yinjiedu.com</p > * <p>Project: concurrency</p > * * @author: qiwei * @Date: 2019/8/20 22:41 * @Version: 1.0 */ public class Singleton07 { //私有構造函數 private Singleton07() { } //單例對象 private static Singleton07 instance = null; public static Singleton07 getInstance() { return Singleton.INSTANCE.getInstance(); } private enum Singleton { INSTANCE; private Singleton07 singleton; Singleton() { singleton = new Singleton07(); } public Singleton07 getInstance() { return singleton; } } }
上面代碼,在使用枚舉構造方法的時候JVM保證這個方法絕對只調用一次,個人推薦這種創建單例的模式。
解決6:餓漢模式之靜態域實現問題
第六種餓漢模式創建單例的方式,主要是因為代碼位置問題引起的,正確寫法是把下麵兩段代碼交換位置:
private static Singleton06 instance = null;
static { instance = new Singleton06(); }
這是在使用靜態域時候特別容易犯的錯誤,希望大家註意。
關於單例模式的分享就到這裡,不足之處希望大家提出來,我會及時修正。
獲取實時信息,關註公眾號:『編程之藝術』,二維碼: