1. 程式人生 > 實用技巧 >單例設計模式(Singleton Pattern)完全解析

單例設計模式(Singleton Pattern)完全解析

2、為什麼會有單例設計模式?

我們都知道單例模式是在開發中用的最多的一種設計模式,那麼究竟為什麼會有單例設計模式呢?對於這個問題相信有很多會寫單例的人都會有個這個疑問。在這裡先說一下單例的用途,然後舉一個例子大家就會明白為什麼會有單例了。單例模式主要是為了避免因為建立了多個例項造成資源的浪費,且多個例項由於多次呼叫容易導致結果出現錯誤,而使用單例模式能夠保證整個應用中有且只有一個例項。從其名字中我們就可以看出所謂單例,就是單個例項也就是說它可以解決的問題是:可以保證一個類在記憶體中的物件的唯一性,在一些常用的工具類、執行緒池、快取,資料庫,賬戶登入系統、配置檔案等程式中可能只允許我們建立一個物件,一方面如果建立多個物件可能引起程式的錯誤,另一方面建立多個物件也造成資源的浪費。在這種基礎之上單例設計模式就產生了因為使用單例能夠保證整個應用中有且只有一個例項,看到這大家可能有些疑惑,沒關係,我們來舉一個例子,相信看完後你就會非常明白,為什麼會有單例。

假如有一個有這麼一個需求,有一個類A和一個類B它們共享配置檔案的資訊,在這個配置檔案中有很多資料如下圖所示

如上圖所示現在類ConfigFile中存在共享的資料Num1,Num2,Num3等。假如在類A中修改ConfigFile中資料,在類A中應該有如下程式碼:

ConfigFile configFile=new ConfigFile();
configFile. Num1=2;

ConfigFile configFile=new ConfigFile();
System. out.println("configFile.Num1=" +configFile.Num1);
即直接new ConfigFile();然後列印Num1,大家思考一下這時候打印出的資料為幾?我想你應該知道它列印的結果是這樣的:configFile.Num1=1;也就是說因為每次呼叫都建立了一個ConfigFile物件,所以導致了在類A中的修改並不會真正改變ConfigFile中的值,它所更改的只是在類A中說建立的那個物件的值。假如現在要求在類A中修改資料後,要通知類B,即在類A和類B中操作的資料是同一個資料,類A改變一個數據,類B也會得到這個資料,並在類A修改後的基礎上進行操作,那麼我們應該怎麼做呢?看到這大家可能會說so easy,把ConfigFile中的資料設定為靜態不就Ok了嗎?對,有這種想法很好,這樣做也沒有錯。但是我們都知道靜態資料的生命週期是很長的,假如ConfigFile中有很多資料時,如果將其全部設成靜態的,那將是對記憶體的極大損耗。所以全部設定成靜態雖然可行但並不是一個很好的解決方法。那麼我們應該怎麼做呢?要想解決上面的問題,其實不難,只要能保證物件是唯一的就可以解決上面的問題,那麼問題來了如何保證物件的唯一性呢?這樣就需要用單例設計模式了。

3、單例模式的設計思想

在上面我們說到現在解決問題的關鍵就是保證在應用中只有一個物件就行了,那麼怎麼保證只有一個物件呢?

其實只需要三步就可以保證物件的唯一性

(1)不允許其他程式用new物件。

因為new就是開闢新的空間,在這裡更改資料只是更改的所建立的物件的資料,如果可以new的話,每一次new都產生一個物件,這樣肯定保證不了物件的唯一性。

(2)在該類中建立物件
因為不允許其他程式new物件,所以這裡的物件需要在本類中new出來

(3)對外提供一個可以讓其他程式獲取該物件的方法

因為物件是在本類中建立的,所以需要提供一個方法讓其它的類獲取這個物件。

那麼這三步怎麼用程式碼實現呢?將上述三步轉換成程式碼描述是這樣的

(1)私有化該類的建構函式
(2)通過new在本類中建立一個本類物件
(3)定義一個公有的方法,將在該類中所建立的物件返回

4、單例模式的寫法
public class Singleton {
 
    private static Singleton instance=new Singleton();
    private Singleton(){};
    public static Singleton getInstance(){
        return instance;
    }
}

Singleton instance = Singleton.getInstance();

優點:從它的實現中我們可以看到,這種方式的實現比較簡單,在類載入的時候就完成了例項化,避免了執行緒的同步問題。

缺點:由於在類載入的時候就例項化了,所以沒有達到Lazy Loading(懶載入)的效果,也就是說可能我沒有用到這個例項,但是它也會載入,會造成記憶體的浪費(但是這個浪費可以忽略,所以這種方式也是推薦使用的)。

4.3單例模式的懶漢式[執行緒不安全,不可用]

public class Singleton {
 
    private static Singleton instance=null;
    
    private Singleton() {};
    
    public static Singleton getInstance(){
        
        if(instance==null){
            instance=new Singleton();
        }
        return instance;
    }
}
這種方式是在呼叫getInstance方法的時候才建立物件的,所以它比較懶因此被稱為懶漢式。 在上述兩種寫法中懶漢式其實是存線上程安全問題的,喜歡刨根問題的同學可能會問,存在怎樣的執行緒安全問題?怎樣導致這種問題的?好,我們來說一下什麼情況下這種寫法會有問題。在執行過程中可能存在這麼一種情況:有多個執行緒去呼叫getInstance方法來獲取Singleton的例項,那麼就有可能發生這樣一種情況當第一個執行緒在執行if(instance==null)這個語句時,此時instance是為null的進入語句。在還沒有執行instance=new Singleton()時(此時instance是為null的)第二個執行緒也進入if(instance==null)這個語句,因為之前進入這個語句的執行緒中還沒有執行instance=new Singleton(),所以它會執行instance=new Singleton()來例項化Singleton物件,因為第二個執行緒也進入了if語句所以它也會例項化Singleton物件。這樣就導致了例項化了兩個Singleton物件。所以單例模式的懶漢式是存線上程安全問題的,既然它存在問題,那麼可能有解決這個問題的方法,那麼究竟怎麼解決呢?對這種問題可能很多人會想到加鎖於是出現了下面這種寫法。 4.7內部類[推薦用]
public class Singleton{
 
    
    private Singleton() {};
    
    private static class SingletonHolder{
        private static Singleton instance=new Singleton();
    } 
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

Singleton instance = Singleton.getInstance();

單例模式在面試中會常常的被遇到,因為它是考察一個程式設計師的基礎的紮實程度的,如果說你跟面試官說你做過專案,面試官讓你寫幾個單例設計模式,你寫不出來,你覺著面試官會相信嗎?在面試時一定要認真準備每一次面試,靠忽悠即使你被錄取了,你也很有可能會對這個公司不滿意,好了我們言歸正傳,其實單例設計模式在面試中很少有人會問餓漢式寫法,一般都會問單例設計模式的懶漢式的執行緒安全問題,所以大家一定要充分理解單例模式的執行緒安全的問題,就這幾種模式花點時間,認真學透,面試中遇到任何關於單例模式的問題你都不會害怕是吧。