1. 程式人生 > >java 雙重檢查單例和靜態內部類單例

java 雙重檢查單例和靜態內部類單例

最近在看imageLoader 原始碼的時候,看到單例採用的雙重檢查機制,但是在看其他大牛寫的程式碼的時候,採用的是靜態的內部類作為單例,在此研究下。

下面是單例的相關內容:

  • 懶漢式
//懶漢式單例類.在第一次呼叫的時候例項化自己   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  

    public static Singleton getInstance() {  
         if (single == null
) { single = new Singleton(); } return single; } }

懶漢式顧名思義,會延遲載入,在第一次使用該單例的時候才會例項化物件出來,第一次呼叫時要做初始化,如果要做的工作比較多,效能上會有些延遲.但是存在一個問題就是執行緒不安全的。

  • 何為執行緒安全

java中的執行緒安全是什麼:
就是執行緒同步的意思,就是當一個程式對一個執行緒安全的方法或者語句進行訪問的時候,其他的不能再對他進行操作了,必須等到這次訪問結束以後才能對這個執行緒安全的方法進行訪問。

什麼叫執行緒安全:
如果你的程式碼所在的程序中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。

或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。

執行緒安全問題都是由全域性變數及靜態變數引起的。
若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則就可能影響執行緒安全。

  • 餓漢式
  //餓漢式單例類.在類初始化時,已經自行例項化   
public class Singleton1 {  
    private Singleton1() {}  
    private static final Singleton1 single = new Singleton1();  
    //靜態工廠方法   
    public static Singleton1 getInstance() {  
        return single;  
    }  
} 

餓漢式本身就是執行緒安全的,餓漢式在類建立的同時就例項化一個靜態物件出來,不管之後會不會使用這個單例,都會佔據一定的記憶體,但是相應的,在第一次呼叫時速度也會更快,因為其資源已經初始化完成。

  • 雙重檢查鎖定

這裡寫圖片描述

這個是imageloader 裡面建立單例使用的原始碼,他採用的就是:在getInstance中做了兩次null檢查,確保了只有第一次呼叫單例的時候才會做同步,這樣也是執行緒安全的,同時避免了每次都同步的效能損耗。

這裡有一個關鍵字我們需要注意 :volatitle

注意:假設沒有關鍵字volatile的情況下,兩個執行緒A、B,都是第一次呼叫該單例方法,執行緒A先執行instance = new Instance(),該構造方法是一個非原子操作,編譯後生成多條位元組碼指令,由於JAVA的指令重排序,可能會先執行instance的賦值操作,該操作實際只是在記憶體中開闢一片儲存物件的區域後直接返回記憶體的引用,之後instance便不為空了,但是實際的初始化操作卻還沒有執行,如果就在此時執行緒B進入,就會看到一個不為空的但是不完整(沒有完成初始化)的Instance物件,所以需要加入volatile關鍵字,禁止指令重排序優化,從而安全的實現單例。

  • 靜態內部類的方式
//採用靜態內部類的方式,作為單例,直接用classLoader(jVM 類載入機制)進行處理非同步加鎖的問題,並減小記憶體消耗
    private static class SingletonHolder {
        private static final RemoteApiHelper INSTANCE = new RemoteApiHelper();
    }

    public static RemoteApiHelper getInstance() {
        return SingletonHolder.INSTANCE;
    }

這個解決方案被稱為Lazy initialization
holder class 模式,這個模式綜合使用了java的類級內部類和多執行緒預設同步鎖的知識,
很巧妙的同時實現了延遲載入和執行緒安全。

1 相應的基礎知識
(1)什麼是類級內部類?
簡單點說,類級內部類指的是,有static修飾的成員內部類。如果沒有static修飾的成員式內
部類被稱為物件級內部類。
(2)類級內部類相當於其外部類的static成分,它的物件與外部類物件間不存在依賴關係,因此
可以直接建立。而物件級內部類的例項,是繫結在外部物件例項中的。
(3)類級內部類中,可以定義靜態的方法。在靜態方法中只能引用外部類中的靜態成員方法或變數。
(4)類級內部類相當於其外部類的成員,只有在第一次被使用的時候才會被裝載。

多執行緒預設同步鎖的知識:
大家都知道,在多執行緒開發中,為了解決併發問題,主要是通過使用synchronized來加互斥鎖進行同步控制,
但是在某些情況下,JVM已經隱含的為您執行了同步,這些情況下就不用自己再來進行同步控制了。
這些情況包括:
(1)由靜態初始化器(在靜態欄位上或static{}塊中的初始化器)初始化資料時
(2)訪問final欄位時
(3)在建立執行緒之前建立物件時
(4)執行緒可以看見它將要處理的物件時

2 解決方案的思路
(1)要想很簡單的實現執行緒安全,可以採用靜態初始化器的方式,它可以由JVM來保證執行緒的安全性。比如前面的餓漢式實現方式。但是這樣一來,不是會浪費一定的空間嗎?因為這種實現方式,會在類裝載的時候就初始化物件,不管你需不需要。
(2) 如果現在有一種方法能夠讓類裝載的時候不去初始化物件,那不就解決問題了?一種可行的方式就是採用類級內部類,在這個類級內部類裡面去建立物件例項。這樣一來,只要不使用到這個類級內部類, 那就不會建立物件例項,從而同步實現延遲載入和執行緒安全。

3.補充說明下他是如何體現 懶載入的(Lazy initialization):

(1)因為內部靜態類是要在有引用了以後才會裝載到記憶體的。所以在你第一次呼叫getInstance()之前,SingletonHolder是沒有被裝載進來的,只有在你第一次呼叫了getInstance()之後,裡面涉及到了return SingletonHolder.instance; 產生了對SingletonHolder的引用,內部靜態類的例項才會真正裝載。這也就是懶載入的意思

  • 關於 JVM來保證執行緒的安全性 這句話的意思:

利用了classloader的機制來保證初始化instance時只有一個執行緒,所以也是執行緒安全的,同時沒有效能損耗。感覺這句話等於沒說,下面我在看classloader 機制的網站,希望可以一起學習進步: