設計模式【1.3】-- 爲何餓漢式單例是線程安全的?

咱們都知道,餓漢式單例是線程安全的,也就是不會初始化的時候建立出兩個對象來,可是爲何呢?java

首先定義一個餓漢式單例以下:安全

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()方法,因此是線程安全的。咱們能夠驗證一下:spa

首先改造一下單例:線程

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模塊----結束");
    }
}

測試代碼:code

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則在等待。對象

image-20201217141915904

待到線程1初始化完成的時候,線程2也不會再進入static代碼塊,而是和線程1取得同一個對象,因而可知,static代碼塊實際上就是線程安全的。blog

image-20201217143603156

相關文章
相關標籤/搜索