單例模式(
Singleton Pattern
)確保一個類只有一個實(shí)例,并且提供一個全局的訪問径玖。
單例模式隨處可見痴脾,比如線程池
、緩存
梳星、對話框
赞赖、日志對象
等,這些時(shí)候如果制造出多個實(shí)例冤灾,程序運(yùn)行就會出現(xiàn)預(yù)期之外的情況前域。
這里可能有疑問,我用全局靜態(tài)變量也能做到一個類只有一個實(shí)例韵吨,為什么要引入這樣一個設(shè)計(jì)模式呢匿垄?原因其實(shí)很簡單,全局靜態(tài)變量會造成資源浪費(fèi):假設(shè)這個類非常消耗資源归粉,程序在運(yùn)行過程中浪蹂,不是每一次都用到這個類垮耳,那就是極大的浪費(fèi)仿野。
類圖
類圖不是目的曾棕,僅僅幫助理解
[圖片上傳失敗...(image-1fb5c0-1527174223215)]
單例模式的類圖很簡單序苏,只有一個類喉前,有一個代表自己實(shí)例的instance
變量佳吞,還有一個提供全局訪問的靜態(tài)方法getInstance()
贮勃。
以下的代碼和思路是針對
Java
語言
單例類型
單例模式分為懶漢式和餓漢式童擎,區(qū)別在于實(shí)例化單例對象的時(shí)機(jī)滴劲。
懶漢式
在懶漢式單例模式實(shí)現(xiàn)中,不管單例是否用到顾复,都會實(shí)例化一個單例對象班挖。典型的寫法如下:
/**
* 單例
* Created by Carlton on 2016/11/21.
*/
class Singleton private constructor()
{
companion object
{
private val instance = Singleton()
fun instance() = instance
}
}
因?yàn)?code>Kotlin和Java在靜態(tài)語法上的不一致,后面的代碼都用
Java
來實(shí)現(xiàn)方便理解
/**
* 單例模式
* Created by Carlton on 2016/11/21.
*/
public class Singleton
{
private Singleton()
{
}
private static Singleton instance = new Singleton();
public static Singleton instance()
{
return instance;
}
}
餓漢式非常簡單芯砸,也不會出現(xiàn)資源占用之外的其他問題萧芙,就不多說给梅。
飽漢式、常規(guī)方法
飽漢式也就是常規(guī)實(shí)現(xiàn)方式比較復(fù)雜双揪,原因是我們用到類實(shí)例的時(shí)候才會去實(shí)例化动羽,這中間會出現(xiàn)各種各樣的情況。
介紹了兩種實(shí)現(xiàn)方式渔期,接下來我們實(shí)現(xiàn)一個常規(guī)的單例模式:
/**
* 單例模式
* Created by Carlton on 2016/11/21.
*/
public class Singleton
{
private Singleton()
{
}
private static Singleton instance = null;
public static Singleton instance()
{
if(instance == null)
{
// 1
instance = new Singleton();
}
return instance;
}
}
在客戶端獲取單例的時(shí)候运吓,檢查對象是否是null
如果是,則實(shí)例化一個疯趟,如果不是則直接返回已有的對象拘哨,如果在單線程的情況下,確實(shí)如此信峻,現(xiàn)在如果倦青,兩個或者兩個以上的線程就有問題了:
- 如果兩個線程都到
1
這個位置 - 那么現(xiàn)在的情況就是
if (instance == null)
判斷的時(shí)候兩個線程都通過了 - 這個時(shí)候
instance
會實(shí)例化兩次,這兩個線程拿到的不是同一個實(shí)例
怎么解決這個問題呢站欺?不慌解決姨夹,先看看一下雙重驗(yàn)證和volatile
雙重檢查和volatile
如果加上線程鎖,好像問題就解決了矾策,先看看加線程鎖怎么寫:
/**
* 單例模式
* Created by Carlton on 2016/11/21.
*/
public class Singleton
{
private Singleton()
{
}
private static Singleton instance = null;
public static Singleton instance()
{
if(instance == null)
{
// 1
synchronized (Singleton.class)
{
// 2
if(instance == null)
{ // 3
instance = new Singleton();
}
}
}
return instance;
}
}
現(xiàn)在看看多線程的情況程序會出現(xiàn)什么問題:
- 如果有兩個線程都到了
1
- 因?yàn)橥芥i的原因磷账,只有一個線程可以先進(jìn)入到
2
- 當(dāng)?shù)谝粋€線程進(jìn)入
3
實(shí)例化一個instance
后,第二個線程進(jìn)入判斷的時(shí)候贾虽,就不會進(jìn)入3
這就是雙重驗(yàn)證逃糟,在C/C++
中,這樣做是沒有問題的蓬豁,但是:雙重檢查對Java語言編譯器不成立绰咽!
原因在于,Java編譯器中地粪,Singleton
類的初始化與instance
變量賦值的順序不可預(yù)料取募,如果一個線程在沒有同步化的條件下讀取instance
引用,并調(diào)用這個對象的方法的話蟆技,可能會發(fā)現(xiàn)對象的初始化過程還沒有完成玩敏,從而造成崩潰。
可能有人會覺得volatile
可以解決問題质礼,修改變量申明:
private static volatile Singleton instance = null;
先看看volatile
是什么旺聚?
volatile
變量具有synchronized
的可見性特性,但是不具備原子特性眶蕉。這就是說線程能夠自動發(fā)現(xiàn)volatile
變量的最新值砰粹。volatile
變量可用于提供線程安全,但是只能應(yīng)用于非常有限的一組用例:多個變量之間或者某個變量的當(dāng)前值與修改后值之間沒有約束造挽。
通過這個描述知道volatile
是一個輕量級的線程同步碱璃,之前出現(xiàn)的問題在于線程沒有同步化的條件下讀取instance
弄痹,現(xiàn)在加上volatile
問題就解決了。但是:JDK1.5之前厘贼,這樣使用雙重檢查還是有問題界酒。
Java中如何正確的實(shí)現(xiàn)單例模式
說了這么多,如何才能正確實(shí)現(xiàn)單例模式呢嘴秸?
- 使用餓漢式
- JDK1.5以后使用帶
volatile
修飾的雙重檢查 - 同步鎖加到方法上:
/**
* 單例模式
* Created by Carlton on 2016/11/21.
*/
public class Singleton
{
private static volatile Singleton instance = null;
private Singleton()
{
}
public static synchronized Singleton instance()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
多說幾句毁欣,如果把同步鎖加到方法上面,代表這個方法同一時(shí)間只有一個線程能夠進(jìn)入方法岳掐,這個時(shí)候后面的線程進(jìn)入就會正常的直接返回instance
實(shí)例凭疮。
總結(jié)
單例模式在思路上是很簡單的模式,也就不提供例子串述,單例模式還有很多單例模式的變種执解,但是核心沒變:一個類只有一個實(shí)例;這個實(shí)例由自己來實(shí)例化纲酗;單例模式?jīng)]有提供公共的構(gòu)造函數(shù)衰腌,所以其他類不能對其實(shí)例化。
需要注意的是觅赊,這個模式的復(fù)雜點(diǎn)在于實(shí)現(xiàn)方式右蕊,如何才能保證在各種情況下只有一個類實(shí)例才是關(guān)鍵點(diǎn)。
不登高山吮螺,不知天之高也饶囚;不臨深溪,不知地之厚也
感謝指點(diǎn)鸠补、交流萝风、喜歡