1. 程式人生 > 實用技巧 >設計模式【1.3】-- 為什麼餓漢式單例是執行緒安全的?

設計模式【1.3】-- 為什麼餓漢式單例是執行緒安全的?

我們都知道,餓漢式單例是執行緒安全的,也就是不會初始化的時候創建出兩個物件來,但是為什麼呢?

首先定義一個餓漢式單例如下:

 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程式碼塊實際上就是執行緒安全的。