引言
單例模式的要點
- 單例類在整個系統的運行過程中只能有一個實例;
- 單例類必須要自行來創建實例(換句話說,在Java中就是不對外暴露構造方法,不能由其他的對象來做實例化,除了自己);
- 單例類必須自行向這個系統提供這個實例。
單例模式的實現
餓漢式單例類
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
可以看出,在在這個類被加載時,靜態變量instance會被初始化,這時候類的私有構造方法會被調用,單例類的唯一實例被創建。需要注意的是:由於構造方法是私有的,單例類不能是不能被繼承的。
懶漢式單例類
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance() {
if (null == instance) {
instance = new LazySingleton();
}
return instance;
}
}
從代碼可以看出,跟餓漢式不一樣的是不是在類被加載的時候就實例化,而是在單例類第一次被引用的時候再實例化。上述代碼還稍微做了一寫措施來保證線程安全。到這裏已經給出了兩種單例實現的模式,但是細心的讀者可能都會發現,這兩種方式對併發的處理並不是那麼的好,單例模式最需要注意的就是線程安全的問題,因爲全程可能有n個對象都在修改單例類對象。
線程安全的單例
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (null == instance) {
instance = new LazySingleton();
}
return instance;
}
}
很明顯,在多線程的環境下,這樣子寫你的系統肯定game over了。我給具體分析下爲什麼這樣寫是錯誤的:
- A首先進入if(null == instance)代碼塊內部,並且開始執行new LazySingleton()語句,此時,instance的值仍然爲null,直到賦值語句執行完畢;
- 線程B不會在if(null == instance)語句外等待,此時還未賦值給instance,if語句成立,它會馬上進入if代碼塊內部,B也開始執行instance = new LazySingleton()語句,創建出第二個實例;
- A的instance = new LazySingleton()執行完畢後,這時候instance不爲null了,第三個線程不會再進入到if代碼塊內部;
- B也創建了一個實例,instance的變量值被覆蓋,但是A引用的之前的instance不會被改變。這時候A和B都各自擁有一個獨立的instance對象,這是不對的。
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance() {
if (null == instance) {
instance = new LazySingleton();
}
return instance;
}
}
加了synchronized關鍵字之後,這個靜態工廠方法都是同步的,當線程A和B同時調用此方法時:- 早到一點點的線程A率先進入此方法,B在方法外部等待;
- A執行instance = new LazySingleton(),創建出實例;
- 方法鎖釋放,線程B進入此方法,此時instance不再爲null,if代碼塊不會再次被執行。線程B取到的instance變量所含有的引用與A是同一個。
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (null == instance) { //第一次檢查,位置1
//多個線程同時到達,位置2
synchronized(LazySingleton.class) {
//這裏只能有一個線程,位置3
if (null == instance) {//第二次檢查,位置4
instance = new LazySingleton();
}
}
}
return instance;
}
}
- 因爲A和B是一批調用者,因此當它們進入此靜態方法工廠時,instance變量爲null,線程A和B幾乎同時到達位置1;
- 假設A先到達位置2,進入synchronized(LazySingleton.class)到達位置3,這是,由於synchronized(LazySingleton.class)的同步限制,B無法到達位置3,只能在位置2等候;
- A執行instance = new LazySingleton()語句,實例化instance,此時B還在位置2處等候;
- A退出synchronized(LazySingleton.class),返回instance,退出靜態工廠方法;
- B進入synchronized(LazySingleton.class)塊,到達位置3,並且到達位置4,這時instance已不爲空,B退出synchronized(LazySingleton.class),返回A創建的instance,退出工廠方法。此時A和B得到的是同一個instance對象。