目錄
引言
開發(fā)中什么設(shè)計(jì)模式最常用? Singleton, Factory, ...
這些常用模式中哪個(gè)最簡(jiǎn)單? Singleton, ...
恭喜你"答對(duì)"了! Singleton確實(shí)是一個(gè)比較"簡(jiǎn)單"的模式
But -- 肯定要有But的, 不然就沒必要有下文了
Singleton如此"簡(jiǎn)單"的模式很多人卻都會(huì)犯錯(cuò)!
教科書版本
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) { // step 1
instance = new Singleton(); // step 2
}
return instance;
}
這個(gè)版本最簡(jiǎn)單, 但是問題也是最多的, 那么有哪些問題呢?
- 線程1: Step 1 (Done) -> Step 2 (Doing)
于此同時(shí)
- 線程2: Step 1 (Done, 因?yàn)閕nstance == null) -> Step 2
這樣就創(chuàng)建了多個(gè)實(shí)例
Synchronized Method版本
public static synchronized Singleton getInstance() {
if (instance == null) { // step 1
instance = new Singleton(); // step 2
}
return instance;
}
這個(gè)版本看起來似乎很安全, 但是
- Synchronized Method同時(shí)只能被一個(gè)線程調(diào)用, 該線程調(diào)用結(jié)束后才能被其他的線程調(diào)用 => 多線程調(diào)用效率受到影響
Synchronized Block版本
public static Singleton getSingleton() {
if (instance == null) { // step 1
synchronized (Singleton.class) { // step 2
instance = new Singleton(); // step 3
}
}
return instance;
}
上面的Synchronized Method方法Synchronized的范圍是整個(gè)方法, 而Synchronized Block方法將Synchronized的范圍縮小為Block
看起來算是個(gè)改進(jìn), 但是卻引入了問題
- 線程1: Step 1 (Done) -> Step 2 (Done) -> Step 3 (Doing)
于此同時(shí)
- 線程2: Step 1 (Done) -> Step 2 (Waiting)
請(qǐng)注意下面的情節(jié), 此時(shí)線程1的Step 3完成了, 即
線程1: ... -> Step 3 (Done)
線程1: ... -> Step 2 (Done, 此時(shí)結(jié)束waiting) -> Step 3
結(jié)果和教科書版本一樣, 又創(chuàng)建了多個(gè)實(shí)例
Double Checked Locking(雙重檢驗(yàn)鎖)版本
此版本是對(duì)上述Synchronized Block版本的改進(jìn), 即在Synchronized Block內(nèi)部又添加了instance == null的判斷
public static Singleton getSingleton() {
if (instance == null) { // step 1
synchronized (Singleton.class) { // step 2
if (instance == null) { // step 3
instance = new Singleton(); // step 4
}
}
}
return instance;
}
寫到這里的時(shí)候, 我已經(jīng)"厭倦了": 不就寫個(gè)單例么, 加這么多判斷, 同步, 保護(hù)難道還有問題不成?!
遺憾的是, 還真是有問題! 問題主要出在Step 4
因?yàn)閕nstance = new Singleton()并非是一個(gè)原子操作
它由以下三個(gè)步驟
temp = allocate() => 分配內(nèi)存
constructor(temp) => 構(gòu)造對(duì)象
instance = temp => 賦值操作
但JVM存在指令重排序(Re-Order)優(yōu)化, 導(dǎo)致以上步驟2(構(gòu)造對(duì)象)和步驟3(賦值操作)的順序并不是固定的!
如果步驟3(賦值操作)先于步驟2(構(gòu)造對(duì)象), 那么有可能發(fā)生的問題是
- 線程1: Step 1 (Done) -> Step 2 (Done) -> Step 3 (Done) -> Step 4 (分配內(nèi)存Done, 賦值操作Done, 構(gòu)造對(duì)象Doing)
于此同時(shí)
- 線程2: Step 1 (return, 因?yàn)榇藭r(shí)instance != null)
但是線程2得到的instance是還沒有完全構(gòu)造的對(duì)象, 后果可想而知
Double Checked Locking(雙重檢驗(yàn)鎖)+volatile版本
此版本是對(duì)上述Double Checked Locking Pattern版本的改進(jìn), 即在instance成員前加上volatile修飾符, 以禁止JVM指令重排序(Re-Order)優(yōu)化
完整的代碼是這樣的
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getSingleton() {
if (instance == null) { // step 1
synchronized (Singleton.class) { // step 2
if (instance == null) { // step 3
instance = new Singleton(); // step 4
}
}
}
return instance;
}
好吧, 饒了一大圈之后, 總算又搞定了一個(gè)和Synchronized Method版本一樣可靠的版本
但是, 可靠不代表效率高, 而且為了創(chuàng)建一個(gè)單例, 寫上面一大坨代碼
又是synchronized, 又是雙重判斷, 最后連volatile都搬出來了, 你喜歡么?
關(guān)于使用volatile修飾符效率的討論和優(yōu)化, 詳細(xì)可以參考Java 單例真的寫對(duì)了么?
Static Factory版本
上述所有版本要么是有缺陷, 要么是效率低, 但是他們都有個(gè)共同的特點(diǎn): Lazy Loaded(懶加載)
如果不考慮Lazy Loaded帶來的這些微小的內(nèi)存消耗和優(yōu)化的話, 下面的版本是我最喜歡的
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
現(xiàn)在知道我為什么最喜歡了吧, 因?yàn)樗娴暮芎?jiǎn)單!
這里的instance成員聲明成static final, 這意味著
在該類被加載至內(nèi)存時(shí)就創(chuàng)建了實(shí)例, 該過程自然是Thread Safe(線程安全)的
Enum版本
這個(gè)版本很"高端", 不過缺點(diǎn)也很明顯: 太"高端", 以至于之前我完全沒有接觸和使用過, 不過為了文章的完整性, 還是在此簡(jiǎn)單討論下吧
public enum Singleton{
INSTANCE;
}
什么? 這就完了? 果然太"高端"! 這么神奇, 原理是怎樣呢? 黑魔法就是
默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的
我們可以通過Singleton來訪問實(shí)例, 使用也是如此簡(jiǎn)單
附錄
更多文章, 請(qǐng)支持我的個(gè)人博客