咱們都知道,餓漢式單例是線程安全的,也就是不會初始化的時候建立出兩個對象來,可是爲何呢?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則在等待。對象
待到線程1初始化完成的時候,線程2也不會再進入static
代碼塊,而是和線程1取得同一個對象,因而可知,static
代碼塊實際上就是線程安全的。blog