目錄
- 1 什么是單例模式?
-
- 2 我對餓漢式的思考
- 3 懶漢式
- 3.1 解決懶漢式的線程安全問題
- 3.1.1 加鎖:synchronized(synchronized修飾靜態方法)
- 3.1.2 對“3.1.1”性能的改進
1 什么是單例模式?
1.1 什么是餓漢式?
1.2 什么是懶漢式?
2 我對餓漢式的思考
public class LearnSingleton {private static LearnSingleton instance = new LearnSingleton();private LearnSingleton() {}public static LearnSingleton getInstance() {return instance;}
}
- 當我們調用LearnSingleton.getInstance()時,會觸發LearnSingleton的加載。在類加載的準備階段,創建好了靜態變量表(此時instance對應的slot槽還為null),等到了初始化階段,開始執行clinit方法,會創建LearnSingleton的示例(執行了
new LearnSingleton()
語句)。類加載后,執行getInstance()方法,將單例返回給用戶。 - 這么一看,上述寫法不也是懶漢式嗎?在使用時才創建實例啊。(雖然,這個實例也是在類加載時就創建好的)
- 要想明白這到底是懶漢式,還是餓漢式,關鍵在于:
public class LearnSingleton {private static LearnSingleton instance = new LearnSingleton();private LearnSingleton() {}public static LearnSingleton getInstance() {return instance;}public static int add(int x, int y) {return x + y;}
}
- 當用戶調用LearnSingleton.add(1, 2)時,在類加載過程中,就已經創建好了單例,但并未使用。因此,這不符合在需要時創建單例的定義。從這個例子,就能想明白了,這種寫法是餓漢式。
- 徹底的餓漢式:
public class LearnSingleton {private static final LearnSingleton instance = new LearnSingleton();private LearnSingleton() {}public static LearnSingleton getInstance() {return instance;}
}
- 只要這個類加載了,由于instance是常量,因此在類加載的準備階段就創建好了單例。這是徹底的餓漢式。可謂“餓瘋了”😃。
- 餓漢式,是在類加載階段完成實例的創建,由JVM保證了線程安全。
3 懶漢式
public class LearnLazySingleton {private static LearnLazySingleton instance;private LearnLazySingleton() {}public static LearnLazySingleton getInstance() {if (instance == null) {instance = new LearnLazySingleton();}return instance;}
}
3.1 解決懶漢式的線程安全問題
3.1.1 加鎖:synchronized(synchronized修飾靜態方法)
public class LearnLazySingleton {private static LearnLazySingleton instance;private LearnLazySingleton() {}public synchronized static LearnLazySingleton getInstance() {if (instance == null) {instance = new LearnLazySingleton();}return instance;}public synchronized static int add(int x, int y) {return x + y;}public static int sub(int x, int y) {return x - y;}
}
- 當線程A先調用getInstance()方法的同時, 另一個線程B嘗試訪問add()方法,線程B會因為沒有LearnLazySingleton的class對象的鎖而等待。如果類中的其他方法不是synchronized的,它們就不會被鎖定,即線程C調用sub方法()就不會等待。
- 本質是因為,在調用同步方法前,只有獲取鎖,才能進入臨界區。而如果不是臨界區,那就不會受影響。
3.1.2 對“3.1.1”性能的改進
- 上面的寫法,有個非常難受的地方,例如,線程A已經調用getInstance()方法,創建好了單例。但線程B為了獲取單例,也不得不調用getInstance()方法(唯一獲取實例的入口),這時候就可能和線程C撞車,因為只要線程C調用add方法(),就可能讓線程B獲取單例時發生阻塞。
- 改進:
public class LearnLazySingleton {private static LearnLazySingleton instance;private LearnLazySingleton() {}public static LearnLazySingleton getInstance() {if (instance == null) {synchronized (LearnLazySingleton.class) {if (instance == null) {instance = new LearnLazySingleton();}}}return instance;}public synchronized static int add(int x, int y) {return x + y;}
}
- 在創建單例前,仍然存在線程A和線程B爭搶著執行“instance = new LearnLazySingleton();”,也會影響線程C去調用add()方法。但一旦實例創建好后,instance不為null,線程們調用getInstance()方法就不會阻塞了。