單線程下的單例模式: public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == ...
單線程下的單例模式:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton()
}
return instance;
}
}
幾個關鍵點:
- static 修飾:表名屬於類而不是類對象,不會每生成一個新的類對象都新生成一份。並且可以在不創建類對象的情況下直接調用。
- 為什麼構造函數是
private
類型?不然呢,開放了構造函數還怎麼單例。 - 為什麼不把單例的邏輯放到構造函數中?在
Singleton()
中調用Singleton()
麽,那不是死迴圈了。 - 類中的單例變數是
private
類型的,不能直接訪問,要通過getInstance()
來獲取。
多線程下的單例模式:
和單線程有什麼區別?
- 需要考慮線程安全問題
- 需要考慮效率問題
方法一:
只需要給 getInstance
方法添加 synchronized
關鍵字即可。
public static synchronized Singleton getInstance() {
問題:每次訪問都要同步,會降低性能。
方法二:
雙重檢查鎖定
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton()
}
}
}
return instance;
}
}
關鍵點:
- 性能優化:將
synchronized
放到實際創建時,只有第一次實例未創建才會同步,後續都不會。 - 為什麼要雙重檢查
instance==null
?第一次檢查完,有可能被別的線程先創建了。 - 為什麼
instance
要用volatile
修飾?- 因為
new Singleton()
不是一個單一的操作,會存在指令重排的問題。 - 1、為
instance
分配記憶體空間。2、初始化instance
。3、將instance
指向分配的記憶體地址。 - 如果指令重拍後,變為了 1-3-2,那麼其他線程可能會拿到一個還沒初始化的
instance
。
- 因為
- 為什麼有了
volatile
還需要synchronized
?- 因為
volatile
不保證原子性。
- 因為