設(shè)計模式之單例補遺
懶漢(雙重檢查鎖定)方式的詳解補充
書接上回扬霜,還是先把代碼再貼一次。
public class LazySingleton {
private static volatile LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized(LazySingleton.class){
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
關(guān)鍵之處還是在聲明instance變量時的volatile關(guān)鍵字而涉。因為咋看起來著瓶,如果沒有這個volatile似乎程序邏輯上也沒有什么漏洞。但是啼县,由于instance = new LazySingleton()的操作并不是原子的材原,而是按以下步驟執(zhí)行:
- 為新對象分配內(nèi)存空間;
- 構(gòu)造并初始化對象季眷;
- 把instance指向步驟1中分配的內(nèi)存空間余蟹。
除此之外,Java運行時環(huán)境的JIT編譯器會做指令重排序操作子刮,即生成的機器指令與字節(jié)碼指令順序不一致客叉。在單線程條件下,即使是步驟2话告、3發(fā)生了重排序兼搏,可以保證最終結(jié)果保證一致,也就是instance指向來初始化后的對象沙郭。而在并發(fā)場景下佛呻,一旦發(fā)生了重排序操作,就有可能出現(xiàn)instance指向了步驟1分配的內(nèi)存病线,單尚未完成步驟2的初始化吓著。
此時就輪到『volatile』閃亮登場了鲤嫡。Java語言提供了一種稍弱的同步機制,即volatile變量绑莺,用來確保將變量的更新操作通知到其他線程暖眼。當(dāng)把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的纺裁,因此不會將該變量上的操作與其他內(nèi)存操作一起重排序诫肠。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值欺缘。
如果希望對java的重排序有更多了解栋豫,可以先讀一下這篇文章。
枚舉實現(xiàn)
雖然已經(jīng)提供了很多種的單例實現(xiàn)方法谚殊,但是(在Java中)最簡潔的莫過于使用枚舉來實現(xiàn)了丧鸯。
public enum EnumSingleton {
INSTANCE;
public void Method() {}
}
不僅代碼簡潔,而且JVM來保證了其線程安全嫩絮,并且無償?shù)靥峁┝诵蛄谢瘷C制丛肢,絕對防止多次實例化,是一種高效安全的單例模式的實現(xiàn)剿干。
但是這種寫法我是很少使用的蜂怎,純屬個人習(xí)慣問題,總覺得寫法上有些怪異怨愤,使用起來有心里負(fù)擔(dān)派敷,而且其它語言中似乎也沒見過有這樣的用法。
擴展單例(多例撰洗?)
之前我們討論的都是純粹的單例篮愉,應(yīng)用程序生命周期中只存在一個實例。而現(xiàn)在我們需要做一下擴展差导,將程序改造為允許在其聲明周期內(nèi)存在指定個數(shù)的實例试躏,是一種類似于數(shù)據(jù)庫連接池這樣的概念。明確概念之后设褐,代碼實現(xiàn)也比較簡單颠蕴。
public class Singleton {
private static int size = 5;
private static ArrayList<E> instanceList = new ArrayList(size);
static {
for(int i = 0; i < size; i++) {
instanceList.add(new Singleton());
}
}
private Singleton(){}
public static Singleton getInstance(){
Random random = new Random();
return (Singleton) instanceList.get(random.nextInt(size));
}
}
后記
關(guān)于單例模式,暫告一段落助析,后面再繼續(xù)聊聊其它的模式犀被。
TO BE CONTINUED
<a rel="license" ></a><br />本作品采用<a rel="license" >知識共享署名-非商業(yè)性使用-相同方式共享 4.0 國際許可協(xié)議</a>進行許可。