1. 定義
單例模式:確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。
- 按照定義來看,也可以設(shè)置一個(gè)全局變量,同樣能實(shí)現(xiàn)要求,但是全局變量卻存在問題,如果將對象賦值給一個(gè)全局變量,那么必須在程序一開始就創(chuàng)建好對象,萬一這個(gè)對象非常耗費(fèi)資源,而程序在這次的執(zhí)行過程中又一直沒有用到它,就形成浪費(fèi)了。
2. 用處
有一些對象只需要一個(gè),例如配置文件,工具類,線程池,緩存,日志對象等等。單例模式保證應(yīng)用中有且只有一個(gè)實(shí)例。
常用的單例模式
(1)懶漢式:指全局的單例實(shí)例在第一次被使用時(shí)構(gòu)建。
(2)餓漢式:指全局的單例實(shí)例在類裝載時(shí)構(gòu)建。
日常我們使用的較多的應(yīng)該是懶漢式的單例,畢竟按需加載才能做到資源的最大化利用。
3. 實(shí)現(xiàn)
原理:利用靜態(tài)類變量、靜態(tài)方法和適當(dāng)?shù)?strong>訪問修飾符。
3.1. 經(jīng)典的單例模式實(shí)現(xiàn)
/**
* 經(jīng)典的單件模式實(shí)現(xiàn)
*/
public class Singleton01 {
// 利用一個(gè)靜態(tài)變量來記錄Singleton類的唯一實(shí)例
private static Singleton01 uniqueInstance;
/*
別的成員變量
*/
// 把構(gòu)造器聲明為私有的,只有從Singleton類內(nèi)部才可以調(diào)用構(gòu)造器
private Singleton01() {
}
//
public static Singleton01 getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton01();
}
return uniqueInstance;
}
/*
別的方法實(shí)現(xiàn)
*/
}
上面是單例模式最經(jīng)典也是最簡單的實(shí)現(xiàn)方法,但是當(dāng)程序使用多線程的時(shí)候就會(huì)出現(xiàn)問題,如下圖所示:

在上圖中的程序就會(huì)出現(xiàn)有多個(gè)對象的情況,這是因?yàn)槭褂昧硕嗑€程,處理該多線程災(zāi)難的一個(gè)方法是把getInstance()變成同步(synchronized)方法。
3.2. 處理多線程
經(jīng)典單例遇到多線程就會(huì)創(chuàng)造出多于一個(gè)的實(shí)例,只要把getInstance()變成同步(synchronized)方法,多線程災(zāi)難就可以輕易地解決了。
/**
* 處理多線程:
* 經(jīng)典單例遇到多線程就會(huì)創(chuàng)造出多于一個(gè)的實(shí)例
* 只要把getInstance()變成同步(synchronized)方法
* 多線程災(zāi)難就可以輕易地解決了
*/
public class Singleton02 {
// 利用一個(gè)靜態(tài)變量來記錄Singleton類的唯一實(shí)例
private static Singleton02 uniqueInstance;
/*
別的成員變量
*/
// 把構(gòu)造器聲明為私有的,只有從Singleton類內(nèi)部才可以調(diào)用構(gòu)造器
private Singleton02() {
}
//
public static synchronized Singleton02 getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton02();
}
return uniqueInstance;
}
/*
別的方法實(shí)現(xiàn)
*/
}
但是這樣問題又來了,為了解決多線程的問題,我們引入了同步(synchronized)方法,但是同步會(huì)降低性能,這又是另一個(gè)問題。
3.3. 改善多線程
經(jīng)典單例遇到多線程會(huì)創(chuàng)造出多于一個(gè)的實(shí)例,但是使用同步(synchronized)方法又會(huì)降低性能,因此可以進(jìn)一步改善多線程。
(1)如果getInstance()的性能對應(yīng)用程序不是很關(guān)鍵,就什么都別做
(2)使用“餓漢式”創(chuàng)建實(shí)例,而不延遲實(shí)例化
這里就用到我們的“餓漢式”了,即,全局的單例實(shí)例在類裝載時(shí)構(gòu)建。
如果應(yīng)用程序總是創(chuàng)建并使用單例對象的實(shí)例,或者在創(chuàng)建和運(yùn)行時(shí)方面的負(fù)擔(dān)不太繁重,就可以使用“餓漢式”創(chuàng)建實(shí)例。
package npu.yyl.pattern.singleton;
/**
* 使用同步(synchronized)方法會(huì)降低性能
* 如果應(yīng)用程序總是創(chuàng)建并使用單例對象的實(shí)例,
* 或者在創(chuàng)建和運(yùn)行時(shí)方面的負(fù)擔(dān)不太繁重,
* 就可以使用“餓漢式”創(chuàng)建實(shí)例
*/
public class Singleton03 {
// 在靜態(tài)初始化器(static initializer)中創(chuàng)建單例
// 這段代碼保證了線程安全(thread safe)
private static Singleton03 uniqueInstance = new Singleton03();
private Singleton03() {
}
//
public static Singleton03 getInstance() {
// 已經(jīng)有實(shí)例了,直接使用它
return uniqueInstance;
}
/*
別的方法實(shí)現(xiàn)
*/
}
利用這個(gè)做法,我們依賴JVM在加載這個(gè)類時(shí)馬上創(chuàng)建此唯一的單例實(shí)例。JVM保證在任何線程訪問uniqueInstance靜態(tài)變量之前,一定先創(chuàng)建此實(shí)例。
(3)用“雙重加鎖”,在getInstance()中減少使用同步
利用雙重檢查加鎖(double-checked locking),首先檢查是否實(shí)例已經(jīng)創(chuàng)建了,如果尚未創(chuàng)建,“才”進(jìn)行同步。這樣一來,只有第一次會(huì)同步,這正是我們想要的。
package npu.yyl.pattern.singleton;
/**
* 利用雙重檢查加鎖(double-checked locking),
* 首先檢查是否實(shí)例已經(jīng)創(chuàng)建了,
* 如果尚未創(chuàng)建,“才”進(jìn)行同步。
* 這樣一來,只有第一次會(huì)同步。
*/
public class Singleton04 {
// 利用一個(gè)靜態(tài)變量來記錄Singleton類的唯一實(shí)例
private volatile static Singleton04 uniqueInstance;
private Singleton04() {
}
public static synchronized Singleton04 getInstance() {
if (uniqueInstance == null) {
// 檢查實(shí)例,如果不存在,就進(jìn)入同步區(qū)塊
synchronized (Singleton04.class) {
// 只有第一次才徹底執(zhí)行這里的代碼
if (uniqueInstance == null) {
// 進(jìn)入?yún)^(qū)塊后,再檢查一次。如果仍是null,才創(chuàng)建實(shí)例
uniqueInstance = new Singleton04();
}
}
}
return uniqueInstance;
}
/*
別的方法實(shí)現(xiàn)
*/
}
volatile關(guān)鍵詞確保,當(dāng)uniqueInstance變量被初始化成Singleton實(shí)例時(shí),多個(gè)線程正確的處理uniqueInstance變量。
這個(gè)方法關(guān)心了性能,大大減少了getInstance()的時(shí)間消耗。