本文從單例模式的一般實現(xiàn)方式開始說起吆你,逐步深入到雙重加鎖實現(xiàn)。
一俊犯、餓漢模式
首先介紹一下最簡單的單例模式——餓漢模式妇多,這種方式在單例類被加載的時候?qū)嵗4a實現(xiàn)如下:
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
餓漢模式的缺點在于燕侠,如果單例對象的創(chuàng)建過程比較耗時者祖,那么應(yīng)用程序的啟動將會比較慢。
二绢彤、懶漢模式
為了克服餓漢模式的缺點七问,將單例對象的創(chuàng)建過程延后到第一次使用單例對象時,這種實現(xiàn)方式被稱為懶漢模式茫舶。代碼實現(xiàn)如下:
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
需要注意的是這種實現(xiàn)方式是線程不安全的械巡。
假設(shè)在單例類被實例化之前,有兩個線程同時在獲取單例對象饶氏,線程1在執(zhí)行完第8行if (instance == null)
后讥耗,線程調(diào)度機(jī)制將 CPU 資源分配給線程2,此時線程2在執(zhí)行第8行 if (instance == null)
時也發(fā)現(xiàn)單例類還沒有被實例化疹启,這樣就會導(dǎo)致單例類被實例化兩次古程。為了防止這種情況發(fā)生,需要對 getInstance()
方法同步喊崖。下面看改進(jìn)后的懶漢模式:
三挣磨、改進(jìn)后的懶漢模式
public class Singleton {
private static Singleton instance;
private Singleton() {
}
// 線程安全的懶漢模式
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
缺點:改進(jìn)后的懶漢模式實現(xiàn)方式中,每次獲取單例對象時都會加鎖荤懂,這樣就會帶來性能損失茁裙。
四、 雙重加鎖(double check)
雙重加鎖實現(xiàn)本質(zhì)也是一種懶漢模式势誊,相比第2種實現(xiàn)方式將會有較大的性能提升呜达。代碼實現(xiàn)如下:
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
就算在單例類被實例化時有多個線程同時通過了第8行代碼 if (instance == null)
的判斷,但同一時間只有一個線程獲得鎖后進(jìn)入臨界區(qū)粟耻。通過第8行判斷的每個線程會依次獲得鎖進(jìn)入臨界區(qū)查近,所以進(jìn)入臨界區(qū)后還要再判斷一次單例類是否已被其它線程實例化眉踱,以避免多次實例化。由于雙重加鎖實現(xiàn)僅在實例化單例類時需要加鎖霜威,所以相較于第2種實現(xiàn)方式會帶來性能上的提升谈喳。另外需要注意的是雙重加鎖要對 instance
域加上 volatile
修飾符。由于 synchronized
并不是對 instance
實例進(jìn)行加鎖(因為現(xiàn)在還并沒有實例)戈泼,所以線程在執(zhí)行完第11行修改 instance
的值后婿禽,應(yīng)該將修改后的 instance
立即寫入主存(main memory
),而不是暫時存在寄存器或者高速緩沖區(qū)(caches
)中大猛,以保證新的值對其它線程可見扭倾。
補(bǔ)充:第9行可以鎖住任何一個對象,要進(jìn)入臨界區(qū)必須獲得這個對象的鎖挽绩。由于并不知道其它對象的鎖的用途霉咨,所以這里最好的方式是對 Singleton.class 進(jìn)行加鎖湃望。