面試官:帶筆了吧,那寫兩種單例模式的實現方法吧沙沙沙刷刷刷~~~ 寫好了java
面試官:你這個是怎麼保證線程安全的,那你知道,volatile 關鍵字? 類加載器?鎖機制????git
點贊+收藏 就學會系列,文章收錄在 GitHub JavaEgg ,N線互聯網開發必備技能兵器譜程序員
單例模式,從我看 《Java 10分鐘入門》那天就聽過的一個設計模式,還被面試過好幾回的設計模式問題,今天一網打盡~~github
有一些對象咱們確實只須要一個,好比,線程池、數據庫鏈接、緩存、日誌對象等,若是有多個的話,會形成程序的行爲異常,資源使用過量或者不一致的問題。你也許會說,這種我用全局變量不也能實現嗎,還整個單例模式,好像你很流弊的樣子,若是將對象賦值給一個全局變量,那程序啓動就會建立好對象,萬一這個對象很耗資源,咱們還可能在某些時候用不到,這就形成了資源的浪費,不合理,因此就有了單例模式。面試
單例模式確保一個類只有一個實例,並提供一個全局惟一訪問點數據庫
用這兩個知識點寫出的單例類就是餓漢式了,初始化類的時候就建立,飢不擇食,餓漢設計模式
public class Singleton { //構造私有化,防止直接new private Singleton(){} //靜態初始化器(static initializer)中建立實例,保證線程安全 private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }
餓漢式是線程安全的,JVM在加載類時立刻建立惟一的實例對象,且只會裝載一次。緩存
Java 實現的單例是一個虛擬機的範圍,由於裝載類的功能是虛擬機的,因此一個虛擬機經過本身的ClassLoader 裝載餓漢式實現單例類的時候就會建立一個類實例。(若是一個虛擬機裏有多個ClassLoader的話,就會有多個實例)安全
懶漢式,就是實例在用到的時候纔去建立,比較「懶」session
單例模式的懶漢式實現方式體現了延遲加載的思想(延遲加載也稱懶加載Lazy Load,就是一開始不要加載資源或數據,等到要使用的時候才加載)
public class Singleton { private static Singleton singleton; private Singleton(){} //解決了線程不安全問題,可是效率過低了,每一個線程想得到類的實例的時候,都須要同步方法,不推薦 public static synchronized Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
public class Singleton { //volatitle關鍵詞確保,多線程正確處理singleton private static volatile Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton ==null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
Double-Check 概念(進行兩次檢查)是多線程開發中常用的,爲何須要雙重檢查鎖呢?由於第一次檢查是確保以前是一個空對象,而非空對象就不須要同步了,空對象的線程而後進入同步代碼塊,若是不加第二次空對象檢查,兩個線程同時獲取同步代碼塊,一個線程進入同步代碼塊,另外一個線程就會等待,而這兩個線程就會建立兩個實例化對象,因此須要在線程進入同步代碼塊後再次進行空對象檢查,才能確保只建立一個實例化對象。
雙重檢查加鎖(double checked locking)線程安全、延遲加載、效率比較高
volatile:volatile通常用於多線程的可見性,這裏用來防止指令重排(防止new Singleton時指令重排序致使其餘線程獲取到未初始化完的對象)。被volatile 修飾的變量的值,將不會被本地線程緩存,全部對該變量的讀寫都是直接操做共享內存,從而確保多個線程能正確的處理該變量。
指令重排是指在程序執行過程當中, 爲了性能考慮, 編譯器和CPU可能會對指令從新排序。
Java中建立一個對象,每每包含三個過程。對於singleton = new Singleton(),這不是一個原子操做,在 JVM 中包含以下三個過程。
可是,因爲JVM會進行指令重排序,因此上面的第二步和第三步的順序是不能保證的,最終的執行順序多是 1-2-3,也多是 1-3-2。若是是 1-3-2,則在 3 執行完畢,2 未執行以前,被另外一個線程搶佔了,這時 instance 已是非 null 了(但卻沒有初始化),因此這個線程會直接返回 instance,而後使用,那確定就會報錯了,因此要加入 volatile關鍵字。
public class Singleton { private Singleton(){} private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.INSTANCE; } }
採用類加載的機制來保證初始化實例時只有一個線程;
靜態內部類方式在Singleton 類被裝載的時候並不會當即實例化,而是在調用getInstance的時候,纔去裝載內部類SingletonInstance ,從而完成Singleton的實例化
類的靜態屬性只會在第一次加載類的時候初始化,因此,JVM幫咱們保證了線程的安全性,在類初始化時,其餘線程沒法進入
優勢:線程安全,利用靜態內部類實現延遲加載,效率較高,推薦使用
enum Singleton{ INSTANCE; public void method(){} }
藉助JDK5 添加的枚舉實現單例,不只能夠避免多線程同步問題,還能防止反序列化從新建立新的對象,可是在枚舉中的其餘任何方法的線程安全由程序員本身負責。還有防止上面的經過反射機制調用私用構造器。不過,因爲Java1.5中才加入enum特性,因此使用的人並很少。
這種方式是《Effective Java》 做者Josh Bloch 提倡的方式。
JDK 中,java.lang.Runtime
就是經典的單例模式(餓漢式)