關於java中的double check lock
阿新 • • 發佈:2019-02-08
實現一個正確的單例模式
在熟悉的單例模式中你或許會遇到下面的方式來實現一個單例:
// version 1
class Singleton {
private static Singleton _INSTANCE
static Singleton getInstance() {
if (_INSTANCE == null) {
_INSTANCE = new Singleton()
}
return _INSTANCE;
}
}
但是這個在多執行緒環境下會有問題:
Problem 1: 這個會建立多個Singleton物件.
那麼我可以加上下面的同步就可以了:
// version 2
class Singleton {
private static Singleton _INSTANCE
static synchronized Singleton getInstance() {
if (_INSTANCE == null) {
_INSTANCE = new Singleton()
}
return _INSTANCE;
}
}
這是一個完全正確的版本, 除了效能比較差.
那麼我們可以直接不使用lazy init, 就可以不需要同步了:
// version 3
class Singleton {
private static final Singleton _INSTANCE = new Singleton()
static Singleton getInstance() {
return _INSTANCE;
}
}
這裡final不是必須的.
static為我們提供了保證(正確的被建立, 建立的物件是完整的) 可以參考:JSR 133 (Java Memory Model) FAQ
恩 這很不錯, 除了 也許我根本不需要它, 但是有可能我們需要用到的時候才建立.
所以, 便引出我們今天的雙重檢查版本:
// version 4
class Singleton {
private static Singleton _INSTANCE
static Singleton getInstance() {
if (_INSTANCE == null) {
synchronized (Singleton.class) {
if (_INSTANCE) {
_INSTANCE = new Singleton()
}
}
}
return _INSTANCE;
}
}
這個程式碼是無法工作的. 因為這個可能讓其他執行緒看到沒有完全構件好的物件:
// 原始程式碼
_INSTANCE = new Singleton()
// 實際上的步驟:
1.allocateMemory -> object
2.Singleton._INSTANCE = object
3.init object attributes
實際上我們對2,3 的步驟是無法保證,
也就是如果2先執行 (指令重排), 那麼其他執行緒可能看到構建了一半的物件.
所以常見的可以在多執行緒下正確工作的單例模式:
1. static init
2. object holder
3. synchronized 在class上同步
//object holder
private static class LazySomethingHolder {
public static Something something = new Something();
}
public static Something getInstance() {
return LazySomethingHolder.something;
}