1. 程式人生 > >Java設計模式之《單例模式》及應用場景

Java設計模式之《單例模式》及應用場景

urn vol 運行 www 同步問題 href 外部 占用 模式

轉載參考:http://www.cnblogs.com/V1haoge/p/6510196.html

所謂單例,指的就是單實例,有且僅有一個類實例,這個單例不應該由人來控制,而應該由代碼來限制,強制單例。

  單例有其獨有的使用場景,一般是對於那些業務邏輯上限定不能多例只能單例的情況,例如:類似於計數器之類的存在,一般都需要使用一個實例來進行記錄,若多例計數則會不準確。

  其實單例就是那些很明顯的使用場合,沒有之前學習的那些模式所使用的復雜場景,只要你需要使用單例,那你就使用單例,簡單易理解。

  所以我認為有關單例模式的重點不在於場景,而在於如何使用。

1、常見的單例模式有兩種創建方式:所謂餓懶漢式與餓漢式

(1)懶漢式

  何為懶?顧名思義,就是不做事,這裏也是同義,懶漢式就是不在系統加載時就創建類的單例,而是在第一次使用實例的時候再創建。

詳見下方代碼示例:

public class LHanDanli {
    //定義一個私有類變量來存放單例,私有的目的是指外部無法直接獲取這個變量,而要使用提供的公共方法來獲取
    private static LHanDanli dl = null;
    //定義私有構造器,表示只在類內部使用,亦指單例的實例只能在單例類內部創建
    private LHanDanli(){}
    //定義一個公共的公開的方法來返回該類的實例,由於是懶漢式,需要在第一次使用時生成實例,所以為了線程安全,使用synchronized關鍵字來確保只會生成單例
public static synchronized LHanDanli getInstance(){ if(dl == null){ dl = new LHanDanli(); } return dl; } }

(2)餓漢式

  又何為餓?餓者,饑不擇食;但凡有食,必急食之。此處同義:在加載類的時候就會創建類的單例,並保存在類中。

詳見下方代碼示例:

public class EHanDanli {
    //此處定義類變量實例並直接實例化,在類加載的時候就完成了實例化並保存在類中
    private
static EHanDanli dl = new EHanDanli(); //定義無參構造器,用於單例實例 private EHanDanli(){} //定義公開方法,返回已創建的單例 public static EHanDanli getInstance(){ return dl; } }

2、雙重加鎖機制

  何為雙重加鎖機制?

  在懶漢式實現單例模式的代碼中,有使用synchronized關鍵字來同步獲取實例,保證單例的唯一性,但是上面的代碼在每一次執行時都要進行同步和判斷,無疑會拖慢速度,使用雙重加鎖機制正好可以解決這個問題:

public class SLHanDanli {
    private static volatile SLHanDanli dl = null;
    private SLHanDanli(){}
    public static SLHanDanli getInstance(){
        if(dl == null){
            synchronized (SLHanDanli.class) {
                if(dl == null){
                    dl = new SLHanDanli();
                }
            }
        }
        return dl;
    }
}

看了上面的代碼,有沒有感覺很無語,雙重加鎖難道不是需要兩個synchronized進行加鎖的嗎?

  ......

  其實不然,這裏的雙重指的的雙重判斷,而加鎖單指那個synchronized,為什麽要進行雙重判斷,其實很簡單,第一重判斷,如果單例已經存在,那麽就不再需要進行同步操作,而是直接返回這個實例,如果沒有創建,才會進入同步塊,同步塊的目的與之前相同,目的是為了防止有兩個調用同時進行時,導致生成多個實例,有了同步塊,每次只能有一個線程調用能訪問同步塊內容,當第一個搶到鎖的調用獲取了實例之後,這個實例就會被創建,之後的所有調用都不會進入同步塊,直接在第一重判斷就返回了單例。至於第二個判斷,個人感覺有點查遺補漏的意味在內(期待高人高見)。

  不論如何,使用了雙重加鎖機制後,程序的執行速度有了顯著提升,不必每次都同步加鎖。

  其實我最在意的是volatile的使用,volatile關鍵字的含義是:被其所修飾的變量的值不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存來實現,從而確保多個線程能正確的處理該變量。該關鍵字可能會屏蔽掉虛擬機中的一些代碼優化,所以其運行效率可能不是很高,所以,一般情況下,並不建議使用雙重加鎖機制,酌情使用才是正理!

3、類級內部類方式

  餓漢式會占用較多的空間,因為其在類加載時就會完成實例化,而懶漢式又存在執行速率慢的情況,雙重加鎖機制呢?又有執行效率差的毛病,有沒有一種完美的方式可以規避這些毛病呢?

  貌似有的,就是使用類級內部類結合多線程默認同步鎖,同時實現延遲加載和線程安全。

public class ClassInnerClassDanli {
    public static class DanliHolder{
        private static ClassInnerClassDanli dl = new ClassInnerClassDanli();
    }
    private ClassInnerClassDanli(){}
    public static ClassInnerClassDanli getInstance(){
        return DanliHolder.dl;
    }
}

 如上代碼,所謂類級內部類,就是靜態內部類,這種內部類與其外部類之間並沒有從屬關系,加載外部類的時候,並不會同時加載其靜態內部類,只有在發生調用的時候才會進行加載,加載的時候就會創建單例實例並返回,有效實現了懶加載(延遲加載),至於同步問題,我們采用和餓漢式同樣的靜態初始化器的方式,借助JVM來實現線程安全。

  其實使用靜態初始化器的方式會在類加載時創建類的實例,但是我們將實例的創建顯式放置在靜態內部類中,它會導致在外部類加載時不進行實例創建,這樣就能實現我們的雙重目的:延遲加載和線程安全。

4、使用

  在Spring中創建的Bean實例默認都是單例模式存在的。

Java設計模式之《單例模式》及應用場景