天天看點

單例設計模式-------懶漢式,餓漢式(超詳細,附代碼)

單例設計模式-------懶漢式,餓漢式

單例設計模式是一種很常見的設計模式

單例設計模式-------懶漢式,餓漢式(超詳細,附代碼)

在這裡介紹兩種單例設計模式 懶漢式與餓漢式

單例設計模式的特點:

1.單例設計模式保證一個類隻有一個執行個體。

2.要提供一個通路該類對象執行個體的全局通路點。

單例設計模式要點

對一些類來說,隻有一個執行個體是很重要的。例如很多時候對于某個系統隻需要擁有一個全局對象,這樣有利于我們協調系統的整體行為。           

複制

再比如說某個伺服器程式中,該伺服器的配置資訊存放在一個檔案中,這些配置資料由一個單例對象統一讀取,然後服務程序中的其他對象

再通過這個單例對象擷取這些配置資訊。進而簡化了在比較複雜的環境下配置管理。

通過上面的介紹,我們可以知道單例模式最重要的就是要保證一個類隻有一個執行個體并且這個類易于被通路,那麼要怎麼做才能保證一個類具有一個執行個體呢?一個全局變量使得一個對象可以被通路,但是這樣做卻不能防止你執行個體化多個對象。

一個更好的辦法就是,讓該類自身負責儲存它的唯一執行個體。并且這個類保證沒有其他的執行個體可以被建立。

怎樣保證一個對象的唯一總結如下:

1.為了避免其它程式過多的建立該類的對象,先禁止其它程式建立該類對象執行個體(将構造器私有化)

2.為了友善其它程式通路該類的對象,隻好在本類中自定義一個對象,由1可知該對象是static的,并對外提供通路方式。

分析舉例

在JAVA中單例設計模式

1.餓漢式如下所示

/**
 * Created by ${wuyupku} on 2019/3/15 12:39
 */
 class Singleton01{
    private static Singleton01  modle = new Singleton01();//聲明對象同時私有化
    private Singleton01(){}//構造函數私有化
    public static Singleton01 getInstance(){//向外聲明通路該類對象的方法
        return modle;
    }

}           

複制

2.懶漢式如下所示

/**
 * Created by ${wuyupku} on 2019/3/15 12:43
 */
 class Singleton02 {
    private static Singleton02 modle = null;//聲明對象,不執行個體化

    private Singleton02()  {}//構造函數私有化
    public static Singleton02 getInstance(){//向外提供通路該類對象的方法
        if (modle == null)
            modle = new Singleton02();
            return modle;

    }
}           

複制

3.測試是否保證了對象的唯一性:

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

/**
 * Created by ${wuyupku} on 2019/3/15 12:47
 */
public class SingletonTest {
    public static void main(String[] args) {
        TestSingleton01();
        TestSingleton02();

    }
    public  static void TestSingleton01(){
        System.out.println("餓漢測試");
        Singleton01 one =  Singleton01.getInstance();
        Singleton01 two =  Singleton01.getInstance();
        System.out.println(one);
        System.out.println(two);
        System.out.println("餓漢one = two"+"\t"+(one == two));
    }
    public  static void TestSingleton02(){
        System.out.println("懶漢測試");
        Singleton02 one =  Singleton02.getInstance();
        Singleton02 two =  Singleton02.getInstance();
        System.out.println(one);
        System.out.println(two);
        System.out.println("懶漢one = two"+"\t"+(one == two));
    }
}           

複制

單例設計模式-------懶漢式,餓漢式(超詳細,附代碼)

到此我們總結兩點:

1.餓漢式這種方式加載類對象,我們稱作:預先加載方式。

2.懶漢式這種方式加載類對象,我們稱作:延遲加載方式。

**問題:**上述程式隻是在隻有主線程一個線程運作下的測試結果,要是在多線程環境下又會出現什麼樣的結果呢?這裡預先說明一下

其實在多線程的環境下,餓漢式是沒有問題的,但是懶漢式這種延遲加載的方式會出現線程安全問題的,下面我們通過例子加以說明。

4.在多線程環境下運作餓漢式與懶漢式
單例設計模式-------懶漢式,餓漢式(超詳細,附代碼)
單例設計模式-------懶漢式,餓漢式(超詳細,附代碼)

觀察上述的結果我們看到在餓漢式的測試中兩個線程所得到的對象時同一個對象,因為@後面的就是對象的HashCode它們相同保證了對象的唯一性,但是在懶漢式中所得到對象不是同一個對象,因為兩個線程所對應的HashCode不同,導緻它們不是同一對象。這是為什麼呢?為什麼餓漢式就保證了對象的唯一性了而懶漢式卻不能保證對象的唯一呢?

這裡我們要分析的就餓漢式和和懶漢式那兩個對象了Singleton1,與Singleton2了。在ThreadSingleton1與ThreadSingleton2的run方法中是線程運作的代碼塊,他們分别調用了餓漢式(Singleton1) 與 懶漢式(Singleton2)中的getInstance方法。在餓漢式中在該類的方法getInstance被調用之前,該類的對象已經存在,假設有兩個線程(t1,t2)在調用該方法,t1與t2無論怎樣調用都不會産生新的對象,因為該對象在方法調用之前就存在了,是伴随類的生成而生成的。

相反在懶漢式的方法getInstance被調用之前,該類的對象不一定存在(至少在第一次被調用的時候該類的對象不存在),是以他每次調用之前要檢測,若對象不存在則生成對象,對象存在則直接傳回。

在多線程環境下問題就出現生成對象的時候,假設有兩個線程(t1,t2)在調用該方法,考慮這種情況,就是當線程t1檢測到該對象不存在的時候,正要準備建立線程(還沒建立),而在這個時候CPU切換到t2上執行權交給t2,t2檢測到該對象不存在,就生成了對象,t2線程結束了,執行權回到t1上,t1這個時候就不需要判斷了,因為開始他判斷了的,他就直接向下執行,生成了對象。這樣就導緻了該類生成了兩個不同的對象,當然這種情況不一定會發生,但這種情況是絕對存在的,也許你運作10000次都不會出現,或許你一運作就會出現該問題。

那麼我們要如何解決這個問題呢?從上面的分析我們可以很清楚的看到,就是要控制當一個線程在操作getInstance方法的時候,不要讓另一個線程操作該方法,而該該類對象Singleton2的對象model被線程t1與線程t2所共享,我們将共享的資源稱作臨界資源,把每個線程通路臨界資源的那段代碼稱作臨界代碼。在作業系統中我們知道可以通過為臨界代碼設定信号量,就可以保證安全的通路共享資源。

在JAVA語言中為了實作這種機制,提供了以下兩個方面的支援:

>>1  為每個對象設定一個“互斥鎖”,這表明,在每一個時刻隻有一個線程持有該互斥鎖,而其他線程若要獲得該互斥鎖,必須等到該線程(持有互斥鎖的線程)将其釋放。
 >>2  為了使用這個“互斥鎖”,在JAVA語言中提供了synchronized關鍵字,這個關鍵字即可修飾函數,也可以修飾代碼,實際上可以将其了解為就是一個鎖,當一個線程執行該臨界代碼的時候,用synchronized給該線程先上鎖,其它線程進不來,當線程代碼執行完了的時候有釋放該鎖,隻不過釋放鎖是隐式的不需要顯示的指明,随代碼的執行完畢,鎖自動的被釋放。           

複制

在JDK5.0後提供了接口Lock,該接口實作了比synchronized方法和語句可獲得更廣泛的鎖操作,而用該接口就可以顯示的指定加鎖 lock.lock() 和解鎖lock.unlock() (lock為實作了Lock的對象執行個體)。 是以我們要将getInstance方法上鎖,在代碼或函數上加關鍵字synchronized修改懶漢式Singleton2中的getInstance代碼,如下

單例設計模式-------懶漢式,餓漢式(超詳細,附代碼)

測試是否懶漢式保證了對象唯一?

單例設計模式-------懶漢式,餓漢式(超詳細,附代碼)

通過觀察我們可以看到保證了對象的唯一,測試成功。

分析代碼,其實可以做一些優化操作,将代碼上鎖操作了後,每次執行都要檢測鎖,效率不高,其實我們可以這樣思考隻有在對象被建立的時候我們才上鎖,保證對象的唯一,一旦對象都存在了,以後操作還需要鎖嘛,不需要了吧,好吧我們就再次優化代碼如下:

單例設計模式-------懶漢式,餓漢式(超詳細,附代碼)

總計

餓漢式:

對象預先加載,線程是安全的,在類建立好的同時對象生成,調用獲得對象執行個體的方法反應速度快,代碼簡練。

懶漢式:

對象延遲加載,效率高,隻有在使用的時候才執行個體化對象,但若設計不當線程會不安全,代碼相對于餓漢式複雜,第一次加載類對象的時候反應不快。