1. 普通實(shí)現(xiàn)(線程不安全)
public class SingleDemo1 {
private static SingleDemo1 instance = null;
// 私有構(gòu)造
private SingleDemo1() {
// do something
}
/**
* fixme: 線程不安全,可能會(huì)重復(fù)創(chuàng)建,導(dǎo)致實(shí)例被覆蓋
*/
public static SingleDemo1 getInstance() {
if (instance == null) {
instance = new SingleDemo1();
}
return instance;
}
}
2. 簡(jiǎn)單加鎖實(shí)現(xiàn)(鎖開(kāi)銷(xiāo)較大)
public class SingleDemo2 {
private static SingleDemo2 instance = null;
// 私有構(gòu)造
private SingleDemo2() {
// do something
}
/**
* fixme: 每次獲取實(shí)例都要加鎖串行,開(kāi)銷(xiāo)較大
*/
public static SingleDemo2 getInstance() {
synchronized(SingleDemo2.class) {
if (instance == null) {
instance = new SingleDemo2();
}
}
return instance;
}
}
3. double check + synchronized
public class SingleDemo3 {
private static SingleDemo3 instance = null;
// 私有構(gòu)造
private SingleDemo3() {
// do something
}
public static SingleDemo3 getInstance() {
if (instance == null) {
synchronized(SingleDemo3.class) {
if (instance == null) {
instance = new SingleDemo3();
}
}
}
return instance;
}
}
內(nèi)外雙層檢查,外層存在競(jìng)態(tài),內(nèi)層可能會(huì)因?yàn)橹噶钪嘏艑?dǎo)致返回未初始化的對(duì)象。解決了可見(jiàn)性,但是未考慮有序性。
原始是因?yàn)椋瑢?code>instance = new SingleDemo3()這句轉(zhuǎn)化為偽代碼指令可表示為:
objRef = allocate(SingleDemo3.class); // 子操作1: 分配對(duì)象所需要的存儲(chǔ)空間
invokeConstructor(objRef); // 子操作2: 初始化objRef引用的對(duì)象
instance = objRef; // 子操作3: 將對(duì)象寫(xiě)入共享的變量
由于JIT編譯器可能將上述的子操作重排序?yàn)椋鹤硬僮鳍佟硬僮鳍邸硬僮鳍冢丛诔跏蓟瘜?duì)象之前將對(duì)象的引用寫(xiě)入實(shí)例變量instance。由于鎖對(duì)有序性的保障是有條件的,而操作①(第1次檢查)讀取instance變量的時(shí)候并沒(méi)有加鎖,因此上述重排序?qū)Σ僮鳍俚膱?zhí)行線程是有影響的:該線程可能看到一個(gè)未初始化(或未初始化完畢)的實(shí)例,即變量instance的值不為null,但是該變量所引用的對(duì)象中的某些實(shí)例變量的變量值可能仍然是默認(rèn)值,而不是構(gòu)造器中設(shè)置的初始值。也就是說(shuō),一個(gè)線程在執(zhí)行操作①的時(shí)候發(fā)現(xiàn)instance不為null,于是該線程就直接返回這個(gè)instance變量所引用的實(shí)例,而這個(gè)實(shí)例可能是未初始化完畢的,這就可能導(dǎo)致程序出錯(cuò)!
4. double check + synchronized + volatile
針對(duì)3中的實(shí)現(xiàn),只需要給共享變量instance
添加volatile關(guān)鍵字即可。volatile關(guān)鍵字解決了兩個(gè)問(wèn)題:
- 保障可見(jiàn)性:任意線程通過(guò)寫(xiě)操作修改了instance的指向,其它線程也可以讀取到對(duì)應(yīng)的修改。
- 保障有序性:由于volatile關(guān)鍵字能夠禁止volatile變量寫(xiě)操作與該操作之前的任何讀寫(xiě)進(jìn)行重排序,因此就不存在將子操作2重排到子操作3之后的情況。
public class SingleDemo4 {
// 添加volatile修飾
private volatile static SingleDemo4 instance = null;
// 私有構(gòu)造
private SingleDemo4() {
// do something
}
public static SingleDemo4 getInstance() {
if (instance == null) {
synchronized(SingleDemo4.class) {
if (instance == null) {
instance = new SingleDemo4();
}
}
}
return instance;
}
}
5. 靜態(tài)內(nèi)部類(lèi)
除了上面的方式之外,還有其它的單例實(shí)現(xiàn)模式,比如靜態(tài)內(nèi)部的方式實(shí)現(xiàn)。
當(dāng)外部調(diào)用getInstance方法時(shí),Inner內(nèi)部類(lèi)會(huì)被jvm加載,由于instance是靜態(tài)變量,所以會(huì)被一同初始化,以此達(dá)到了lazy初始化的同時(shí)也保證了線程安全。
public class SingleDemo5 {
private static class Inner {
final static SingleDemo5 instance = new SingleDemo5();
}
// 私有構(gòu)造
private SingleDemo5() {
// do something
}
public static SingleDemo5 getInstance() {
return Inner.instance;
}
}
6. 枚舉實(shí)現(xiàn)
枚舉類(lèi)型SingleDemo6相當(dāng)于一個(gè)單例類(lèi),其字段INSTANCE值相當(dāng)于該類(lèi)的唯一實(shí)例。這個(gè)實(shí)例是在SingleDemo6.INSTANCE初次被引用的時(shí)候才被初始化的。僅訪問(wèn)SingleDemo6本身(比如上述的SingleDemo6.class.getName()調(diào)用)并不會(huì)導(dǎo)致SingleDemo6的唯一實(shí)例被初始化。
public enum SingleDemo6 {
INSTANCE;
// 私有構(gòu)造
SingleDemo6() {
// init
}
// 外部通過(guò)枚舉實(shí)例訪問(wèn)即可
public void doSomething(){
// do something
}
}
引用自:黃文海. Java多線程編程實(shí)戰(zhàn)指南(核心篇) (Java多線程編程實(shí)戰(zhàn)系列) (Chinese Edition) (Kindle Locations 2435-2437). 電子工業(yè)出版社. Kindle Edition.