1. 程式人生 > 其它 >淺談單例模式之懶漢式與餓漢式

淺談單例模式之懶漢式與餓漢式

java中單例模式是一種常見的設計模式,單例模式的寫法有好幾種,這裡主要介紹兩種:懶漢式單例和餓漢式單例。

單例模式有以下特點:
  1、單例類只能有一個例項。
  2、單例類必須自己建立自己的唯一例項。
  3、單例類必須給所有其他物件提供這一例項。
  單例模式確保某個類只有一個例項,而且自行例項化並向整個系統提供這個例項。在計算機系統中,執行緒池、快取、日誌物件、對話方塊、印表機、顯示卡的驅動程式物件常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個印表機,但只能有一個Printer Spooler,以避免兩個列印作業同時輸出到印表機中。每臺計算機可以有若干通訊埠,系統應當集中管理這些通訊埠,以避免一個通訊埠同時被兩個請求同時呼叫。總之,選擇單例模式就是為了避免不一致狀態,避免政出多頭。

其實我們在實現單例模式的時候只需要記住三點,單例模式就能輕鬆的寫出來

1、私有的建構函式

2、私有靜態單例變數

3、公有靜態getInstance方法

下面來看看具體的實現:

一、餓漢式單例

/*
	餓漢式在類建立的同時就例項化一個靜態物件出來,不管之後會不會使用這個單例,都會佔據一定的記憶體,但是相應的,在第一次呼叫時速度也會更快,因為其資源已經初始化完成。
*/
public class Hungry {
    //餓漢式在類建立的同時就已經建立好一個靜態的物件供系統使用,以後不再改變,所以天生是執行緒安全的。
    private static final Hungry hungry = new Hungry();

    private Hungry() {

    }

    public static Hungry getInstance() {
        return hungry;
    }
}

二、懶漢式單例

1.雙重檢驗鎖

public class lazyMan {
    private volatile static lazyMan lazyMan;

    private lazyMan(){

    }

    public static lazyMan getInstance(){
        //第一重檢驗,目的是為了提高效率:當物件已經被例項化之後,執行緒就不需要再進行競爭鎖了,直接返回lazyMan物件例項就可以。
        if (lazyMan==null){
            synchronized(lazyMan.class){
				//第二重檢驗
                if (lazyMan==null){
                    lazyMan=new lazyMan();
                }
            }
        }
        return lazyMan;
    }
}

/*
	第二重檢驗鎖是為了保證執行緒同步,假若執行緒A通過了第一次判斷,進入了同步程式碼塊,但是還未執行,執行緒B就進來了(執行緒B獲得CPU時間片),執行緒B也通過了第一次判斷(執行緒A並未建立例項,所以B通過了第一次判斷),準備進入同步程式碼塊,假若這個時候不判斷,就會存在這種情況:執行緒B建立了例項,此時恰好A也獲得執行時間片,如果不加以判斷,那麼執行緒A也會建立一個例項,就會造成多例項的情況。
*/

在雙重檢驗鎖實現單例模式中還有比較重要的一點就是宣告變數時必須要用volatile修飾,原因如下:

1.1 volatile關鍵字的作用

1. 可見性
2. 不保證原子性
3. 禁止指令重排

指令重排:你寫的程式,計算機並不是按照你寫的那樣去執行的。
原始碼-->編譯器優化的重排--> 指令並行也可能會重排--> 記憶體系統也會重排---> 執行

比如:

int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4

我們所期望的:1234 但是可能執行的時候回變成 2134 1324

1.2 必須用volatile修飾的原因

在這裡用volatile修飾的原因就是為了禁止指令重排,因為物件的建立並非一步完成,而是需要分為3個步驟執行的。

1. 分配記憶體空間
2、執行構造方法,初始化物件
3、把這個物件指向這個空間

我們期望的步驟是 1-> 2 ->3
但是經過指令重排後可能會變成: 1-> 3 -> 2
這在單執行緒情況下是沒有問題的,但是在多執行緒情況下可能會導致一個執行緒得到了一個空物件。

比如: 執行緒A正常建立一個例項,執行了1-3,此時執行緒B呼叫getInstance()後發現instance不為空,因此會直接返回instance,但此時			instance並未被初始化,所以需要用volatile關鍵字修飾。

2. 靜態內部類

public class Singleton{

    private Singleton(){}

    public static Singleton getInstance(){
        return InnerClass.instance;
    }

    private static class InnerClass{
        public static Singleton instance = new Singleton();
    }
}

靜態內部類實現單例模式其實是利用了類載入機制來保證初始化instance時只有一個執行緒,因為靜態變數在類載入的過程中就被JVM分配了記憶體空間,不需要再進行賦值。