在開發中常常用到單例模式,單例模式也算是設計模式中最容易理解,也是最容易手寫代碼的模式,因此也常做爲面試題來考。因此想總結一下單例模式的理論知識,方便同窗們面試使用。面試
單例模式實現的方式只有兩種類型,一種是餓漢式(類加載時就初始化)、一種是懶漢式(類加載時不初始化)。餓漢式沒什麼可講究的由於它既簡單也線程安全,若是條件容許通常咱們都會直接用餓漢式;惟獨比較麻煩的是懶漢式,考慮到線程安全,使用懶漢式的單例模式,就有多種實現方式了。設計模式
1、餓漢式安全
如上所述,餓漢式很簡單,實現方式以下:多線程
public class Singleton{ private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
這種單例模式,很簡單並且還安全,能夠說近乎完美。它惟一的缺陷就是,這種實現方式就沒法適用於單例還須要必定的配置或者傳參的場景。函數
2、懶漢式性能
懶漢式的單例模式,因爲要考慮到線程安全的狀況,有多種實現方式。優化
0、以下的實現方式是一種不安全的實現方式this
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
之因此把定義爲編號0,就是除非你很是肯定沒有多線程的場景,由於當有多個線程並行調用 getInstance() 的時候,就會建立多個實例。因此絕大部分場景下,這種實現方式都是不該該使用。spa
一、低性能的懶漢式線程
public static synchronized Singleton getInstance() { private static Singleton instance; private Singleton (){} if (instance == null) { instance = new Singleton(); } return instance; }
這種實現方式,解決了多線程實例問題,可是因爲直接在方法上使用synchronized關鍵字,即類鎖同步,使得只能使用每次調用都要進行同步操做,性能比較低。
二、雙重檢驗鎖的懶漢式
public class Singleton { private volatile static Singleton instance; private Singleton() { } public static Singleton getSingleton() { if (instance == null) { // null 檢測
synchronized (Singleton.class) { // 同步檢測
if (instance == null) { instance = new Singleton(); } } } return instance; } }
咱們看到最多的狀況就是沒有加volatile的情形,其實你不加這個關鍵字,面試你的人應該也不會糾結於這個,由於99.99%的狀況都是好的(要知道那些提供後臺服務的,通常只會保證99%可靠性……)。之因此加是由於JVM存在指令重排的優化,致使
new Singleton() 不是一個原子操做行爲,new Singleton() 這句話執行的過程大概分爲如下abc步驟:
a, 給 instance 分配內存;
b, 調用 Singleton 的構造函數來初始化成員變量;
c, 將instance對象指向分配的內存空間(執行完這步 instance 就爲非 null 了);
JVM 的即時編譯器中存在指令重排序的優化,執行順序多是 abc 也多是 acb。若是是acb,在執行到c,被線程X搶佔了,這時 instance 已是非 null 了(但卻沒有初始化),因此線程X會直接返回 instance,使用一個沒有初始化的實例產生錯誤是必然的。那爲何要加volatile呢?通常咱們只用到volatile的可見性功能,即對一個volatile變量的讀,老是能看到(任意線程)對這個volatile變量最後的寫入,並且常常用於解決同步問題。其實volatile還有一個重要的功能——禁止指令重排優化,也就是volatile標記的變量不會被編譯器優化。如上所示,加上volatile之後,讀操做必定會發生在abc或者acb以後,而且這個順序是固定。
三、靜態內部類實現懶漢式
public class Singleton { private static class InternalSingleton { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return InternalSingleton.INSTANCE; } }
因爲InternalSingleton 是私有的,除了 getInstance() 以外沒有辦法訪問它,所以它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷。
四、經過枚舉實現懶漢式
經過枚舉寫單例超級簡單,什麼都不用想,這也是它最大的優勢。下面這段代碼就是聲明枚舉實例的一般作法。
public enum Singleton { INSTANCE; public void show(int age) { System.out.println("this is show function -> " + age); } }
咱們能夠經過Singleton.INSTANCE來訪問實例,這比調用getInstance()方法簡單多了。建立枚舉默認就是線程安全的,因此不須要擔憂double checked locking,並且還能防止反序列化致使從新建立新的對象。可能因爲平時咱們使用枚舉都是爲了表示類別,你們都不多使用這種方式去寫單例模式。