設計模式【1.3】-- 為什麼餓漢式單例是執行緒安全的?
阿新 • • 發佈:2020-12-26
我們都知道,餓漢式單例是執行緒安全的,也就是不會初始化的時候創建出兩個物件來,但是為什麼呢?
首先定義一個餓漢式單例如下:
public class Singleton {
// 私有化構造方法,以防止外界使用該構造方法建立新的例項
private Singleton(){
}
// 預設是public,訪問可以直接通過Singleton.instance來訪問
static Singleton instance = new Singleton();
}
之所以是執行緒安全的,是因為JVM在類載入的過程,保證了不會初始化多個static
物件。類的生命週期主要是:
載入-->驗證-->準備-->解析-->初始化-->使用-->解除安裝
上面的程式碼,實際上類成員變數instance
是在初始化階段的時候完成初始化,所有的類變數以及static
靜態程式碼塊,都是在一個叫clinit()
的方法裡面完成初始化。這一點,使用jclasslib
可以看出來:
clinit()
方法是由虛擬機器收集的,包含了static
變數的賦值操作以及static
程式碼塊,所以我們程式碼中的static Singleton instance = new Singleton();
就是在其中。虛擬機器本身會保證clinit()
程式碼在多執行緒併發的時候,只會有一個執行緒可以訪問到,其他的執行緒都需要等待,並且等到執行的執行緒結束後才可以接著執行,但是它們不會再進入clinit()
首先改造一下單例:
public class Singleton { // 私有化構造方法,以防止外界使用該構造方法建立新的例項 private Singleton() { } // 預設是public,訪問可以直接通過Singleton.instance來訪問 static Singleton instance = null; static { System.out.println("初始化static模組---開始"); instance = new Singleton(); try { System.out.println("初始化中..."); Thread.sleep(20 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("初始化static模組----結束"); } }
測試程式碼:
import java.io.*;
import java.lang.reflect.InvocationTargetException;
public class SingletonTests {
public static void main(String[] args) throws Exception, InvocationTargetException, InstantiationException, NoSuchMethodException {
Thread thread1 = new Thread(new Runnable() {
public void run() {
System.out.println("執行緒1開始嘗試初始化單例");
Singleton singleton = Singleton.instance;
System.out.println("執行緒1獲取到的單例:" + singleton);
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
System.out.println("執行緒2開始嘗試初始化單例");
Singleton singleton = Singleton.instance;
System.out.println("執行緒2獲取到的單例:" + singleton);
}
});
thread1.start();
thread2.start();
}
}
執行結果,一開始執行的時候,我們可以看到執行緒1進去了static
程式碼塊,它在初始化,執行緒2則在等待。
待到執行緒1初始化完成的時候,執行緒2也不會再進入static
程式碼塊,而是和執行緒1取得同一個物件,由此可見,static
程式碼塊實際上就是執行緒安全的。