單例模式
單例模式:確保一個(gè)類最多只有一個(gè)實(shí)例筐摘,并提供一個(gè)全局訪問點(diǎn)
有些對(duì)象我們只需要一個(gè):線程池、緩存咖熟、硬件設(shè)備等
如果多個(gè)實(shí)例會(huì)有造成沖突、結(jié)果的不一致性等問題
單例模式的類圖:
由類圖轉(zhuǎn)化的代碼
#Singleton.java
public class Singleton {
private static Singleton uniqeInstance=null;
//私有的構(gòu)造方法辜昵,杜絕了在外界通過new來實(shí)例化對(duì)象
private Singleton(){
};
//在類方法中,專門用來構(gòu)造Singleton對(duì)象堪置。并將該單例對(duì)象暴露給外界
public static Singleton getInstance()
{
if(uniqeInstance==null)
{
uniqeInstance=new Singleton();
}
return uniqeInstance;
}
}
存在的問題: 在多線程的情況下,如果多個(gè)線程同時(shí)調(diào)用getInstance()方法舀锨,則有可能會(huì)創(chuàng)建多個(gè)實(shí)例對(duì)象
多線程下可能創(chuàng)建多個(gè)實(shí)例對(duì)象的解決方案
同步(synchronized)getInstance方法
private static Singleton uniqeInstance = null;
public synchronized static Singleton getInstance()
{
if(uniqeInstance==null)
{
uniqeInstance=new Singleton();
}
return uniqeInstance;
}
缺點(diǎn): 每一次調(diào)用getInstance() 都會(huì)使用鎖機(jī)制宛逗,會(huì)非常消耗資源。
“急切”創(chuàng)建實(shí)例(餓漢式)
靜態(tài)成員變量不再為null,而是一開始就為它賦值一個(gè)實(shí)例
在這種情況下雷激,類一被加載,靜態(tài)成員變量就被初始化了屎暇。 如果程序中的單例對(duì)象全都采用這種方法,但某些單例對(duì)象從來沒被使用過根悼, 那么就會(huì)造成內(nèi)存的浪費(fèi)。
public class Singleton {
private static Singleton uniqeInstance=new Singleton();
//私有的構(gòu)造方法挤巡,杜絕了在外界通過new來實(shí)例化對(duì)象
private Singleton(){
};
//在類方法中,專門用來構(gòu)造Singleton對(duì)象
public synchronized static Singleton getInstance()
{
return uniqeInstance;
}
}
雙重檢查加鎖(延遲加載)
public volatile static ChocolateFactory uniqueInstance = null;
public static ChocolateFactory getInstance() {
if (uniqueInstance == null) { //第一重檢測(cè)
synchronized (ChocolateFactory.class) {
if (uniqueInstance == null) { //第二重檢測(cè)
uniqueInstance = new ChocolateFactory();
}
}
}
return uniqueInstance;
}
- 如果第一次實(shí)例化時(shí),有多個(gè)線程同時(shí)通過第一重檢測(cè),第二重檢測(cè)保證了只能有一個(gè)線程實(shí)例化成功.其他競(jìng)爭(zhēng)失敗的線程直接返回 已創(chuàng)建的實(shí)例
- 此后所有的實(shí)例化經(jīng)過第一重檢測(cè)就直接返回
- volatile 存在的意義 (見下, 詳細(xì)可參考并發(fā)編程與藝術(shù))
上訴代碼是一個(gè)錯(cuò)誤的方案喉恋。在線程執(zhí)行到第4行時(shí)母廷,代碼讀到instance不為null時(shí),instance引用的對(duì)象有可能還沒有完成初始化
-
問題的根源
-
代碼的第七行 instance = new Instance(); 創(chuàng)建了一個(gè)對(duì)象徘意,這一行代碼可以分解為如下3行偽代碼
- memory = allocate(); //1. 分配對(duì)象的內(nèi)存空間
- initInstance(memory) //2. 初始化對(duì)象
- instance = memory ; //3. 設(shè)置instance指向剛分配的內(nèi)存地址
實(shí)際上,上面3行代碼的2和3有可能發(fā)生重排序玖详。 那么在多線程的情況下就有可能 訪問到未初始化的對(duì)象把介!
解決方案蟋座,使用volatile修飾成員變量instance。第2/3步將會(huì)被禁止重排序向臀!
-