【設(shè)計(jì)模式】秒懂單例模式
單例模式介紹
概述
單例模式:某一個(gè)類在系統(tǒng)中只需要有一個(gè)實(shí)例對(duì)象,而且對(duì)象是由這個(gè)類自行實(shí)例化并提供給系統(tǒng)其它地方使用,這個(gè)類稱為單例類。單例模式是GOF 23種設(shè)計(jì)模式中最簡(jiǎn)單的一種,但同時(shí)也是在項(xiàng)目中接觸最多的一種。單例模式屬于一種創(chuàng)建型設(shè)計(jì)模式。
使用場(chǎng)景
大家都使用過Windows任務(wù)管理器,正常情況下,無(wú)論我們?cè)赪indows任務(wù)欄的右鍵菜單上點(diǎn)擊啟動(dòng)多少次“任務(wù)管理器”,系統(tǒng)始終只能彈出一個(gè)任務(wù)管理器窗口。也就是說(shuō),在一個(gè)Windows系統(tǒng)中,系統(tǒng)只維護(hù)一個(gè)任務(wù)管理器。這就是一個(gè)典型的單例模式運(yùn)用。
再舉一個(gè)例子,網(wǎng)站的計(jì)數(shù)器,一般也是采用單例模式實(shí)現(xiàn),如果你存在多個(gè)計(jì)數(shù)器,每一個(gè)用戶的訪問都刷新計(jì)數(shù)器的值,這樣的話你的實(shí)計(jì)數(shù)的值是難以同步的。但是如果采用單例模式實(shí)現(xiàn)就不會(huì)存在這樣的問題,而且還可以避免線程安全問題。同樣多線程的線程池的設(shè)計(jì)一般也是采用單例模式,這是由于線程池需要方便對(duì)池中的線程進(jìn)行控制。
可以看出,我們?cè)诔绦蛑惺褂脝卫J剑康囊话闶翘幚碣Y源訪問的沖突,或者從業(yè)務(wù)概念上,有些數(shù)據(jù)在系統(tǒng)中只應(yīng)保存一份,那也比較適合設(shè)計(jì)為單例類,比如配置類、全局流水號(hào)生成器等。
UML類圖

單例模式實(shí)現(xiàn)
單例模式實(shí)現(xiàn)要點(diǎn)
單例模式雖然簡(jiǎn)單,但是要寫出一個(gè)能保證在多線程環(huán)境下也能保證實(shí)例唯一性的單例確不是那么簡(jiǎn)單,實(shí)現(xiàn)一個(gè)正確的單例模式有以下幾個(gè)要點(diǎn):
? 1.某個(gè)類只能有一個(gè)實(shí)例,即使是多線程運(yùn)行環(huán)境下;
? 2.單例類的實(shí)例一定是單例類自身創(chuàng)建,而不是在單例類外部用其它方式如new方式創(chuàng)建;
? 3.單例類需要提供一個(gè)方法向整個(gè)系統(tǒng)提供這個(gè)實(shí)例對(duì)象。
單例兩種模式
單例模式分為餓漢模式和懶漢模式,這兩種模式很好理解,懶漢模式的意思就是這個(gè)類很懶,只要?jiǎng)e人不找它要實(shí)例,它都懶得創(chuàng)建。餓漢模式在初始化時(shí),我們就創(chuàng)建了唯一的實(shí)例,即便這個(gè)實(shí)例后面并不會(huì)被使用。
下面分別介紹兩種單例模式的寫法。
懶漢式
下面這種寫法的單例是大家最簡(jiǎn)單最容易寫出的一種單例寫法,只適用于單線程的系統(tǒng),也就是說(shuō)它不是線程安全的。
//懶漢式,線程不安全
class Singleton1{
private static Singleton1 instance;
//構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實(shí)例
private Singleton1(){
}
//系統(tǒng)使用單例的入口
public static Singleton1 getInstance(){
if (null == instance){
instance = new Singleton1();
}
return instance;
}
}針對(duì)線程不安全的問題,可以通過獲取實(shí)例的方法添加了synchronized來(lái)解決,如下:
//懶漢式,線程安全,效率低
class Singleton2{
private static Singleton2 instance;
//構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實(shí)例
private Singleton2(){
}
//系統(tǒng)使用單例的入口
public staticsynchronizedSingleton2 getInstance(){
if (null == instance){
instance = new Singleton2();
}
return instance;
}
}
這樣一來(lái),確實(shí)線程安全了,但是又帶來(lái)了另一個(gè)問題:程序的性能極大的降低了,高并發(fā)下多個(gè)線程去獲取這個(gè)實(shí)例,現(xiàn)在卻要排隊(duì)。
針對(duì)性能問題,有同學(xué)想到了減小synchronized的粒度,不加在方法上,而是放在代碼塊中:
//懶漢式,線程不安全
class Singleton3{
private static Singleton3 instance;
//構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實(shí)例
private Singleton3(){
}
//系統(tǒng)使用單例的入口
public static Singleton3 getInstance(){
if (null == instance){
synchronized(Singleton3.class){
instance = new Singleton3();
}
}
return instance;
}
}
但是,很不幸,如果改成這樣,又變得線程不安全了,我們?cè)囍治鲆粋€(gè)代碼執(zhí)行的場(chǎng)景:假設(shè)我們有兩個(gè)線程 T1與T2并發(fā)訪問getInstance方法。當(dāng)T1執(zhí)行完if (instance == null)且instance為null時(shí),其CUP執(zhí)行時(shí)間被T2搶占,所以T1還沒有創(chuàng)建實(shí)例。T2也執(zhí)行if (instance == null),此時(shí)instance肯定還為null,T2執(zhí)行創(chuàng)建實(shí)例的代碼,當(dāng)T1再次獲得CPU執(zhí)行時(shí)間后,其從synchronized 處恢復(fù),又會(huì)創(chuàng)建一個(gè)實(shí)例。
那么有沒有一種寫法,可以同時(shí)兼顧到效率和線程安全兩方面了,還真有,就是我們下面將要介紹的double-check的方式。
////懶漢式,線程安全,效率還可以
class Singleton4{
//注意加上volatile關(guān)鍵字
private static volatile Singleton4 instance;
//構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實(shí)例
private Singleton4(){
}
//系統(tǒng)使用單例的入口
public static Singleton4 getInstance(){
//第一次檢查提高訪問性能
if (null == instance){
synchronized(Singleton4.class) {
//第二次檢查為了線程安全
if(instance ==null) {
instance = new Singleton4();
}
}
}
return instance;
}
}
這種單例的寫法做了兩次 if (null == instance)的判斷,因此被稱為double-check的方式。
? 第一次check為了提高訪問性能。因?yàn)橐坏?shí)例被創(chuàng)建,后面線程的所有的check都為假,不需要執(zhí)行synchronized競(jìng)爭(zhēng)鎖了。
? 第二次check是為了線程安全,確保多線程環(huán)境下只生成一個(gè)實(shí)例。
需要注意的是,這種方式,在定義實(shí)例時(shí)一定需要加上volatile 關(guān)鍵字,禁止虛擬機(jī)指令重排,否則,還是有一定幾率會(huì)生成多個(gè)實(shí)例,關(guān)于volatile 關(guān)鍵字和指令重排的問題這里不過多介紹,后面在多線程安全系列文章中再詳細(xì)介紹。
餓漢式
使用靜態(tài)常量在類加載時(shí)候就創(chuàng)建了實(shí)例,屬于餓漢模式。其是線程安全的,這一點(diǎn)由JVM來(lái)保證。
//餓漢式,線程安全
class Singleton5{
//
private static final Singleton5 INSTANCE = new Singleton5();
//構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實(shí)例
private Singleton5(){
}
//系統(tǒng)使用單例的入口
public static Singleton5 getInstance(){
return INSTANCE;
}
}
本文源碼地址:
https://github.com/qinlizhong1/javaStudy/tree/master/DesignPattern/src/singleton
本文示例代碼環(huán)境:
操作系統(tǒng):macOs 12.1
JDK版本:12.0.1
maven版本: 3.8.4
推薦閱讀:
完全整理 | 365篇高質(zhì)技術(shù)文章目錄整理
專注服務(wù)器后臺(tái)技術(shù)棧知識(shí)總結(jié)分享
歡迎關(guān)注交流共同進(jìn)步
