天天看點

設計模式:如何優雅地手寫單例模式

單例模式是設計模式中最簡單的形式之一。這一模式的目的是使得類的一個對象成為系統中的唯一執行個體。正是因為簡單,也成為面試中的衆矢之的。本文來手寫單例模式。

單例模式是一種常用的設計模式,該模式提供了一種建立對象的方法,確定在程式中一個類最多隻有一個執行個體。

單例有什麼用處?

有一些對象其實我們隻需要一個,比如線程池、緩存、對話框、處理偏好設定和系統資料庫的對象、日志對象,充當列印機、顯示等裝置的驅動程式對象。其實,這類對象隻能有一個執行個體,如果制造出來多個執行個體,就會導緻許多問題,如:程式的行為異常、資源使用過量,或者是不一緻的結果。

Singleton通常用來代表那些本質上唯一的系統元件,比如視窗管理器或者檔案系統。

在Java中實作單例模式,需要一個靜态變量、一個靜态方法和私有的構造器。

對于一個簡單的單例模式,可以這樣實作:

定義一個私有的靜态變量uniqueInstance;

定義私有的構造方法。這樣别處的代碼無法通過調用該類的構造函數來執行個體化該類的對象,隻能通過該類提供的靜态方法來得到該類的唯一執行個體;

提供一個getInstance()方法,該方法中判斷是否已經存在該類的執行個體,如果存在直接傳回,不存在則建立一個再傳回。代碼如下:

這段代碼使用了延遲執行個體化,在單線程中沒有任何問題。但是在多線程環境下,當有多個線程并行調用 getInstance(),都認為uniqueInstance為null的時候,就會調用<code>uniqueInstance = new Singleton();</code>,這樣就會建立多個Singleton執行個體,無法保證單例。

解決多線程環境下的線程安全問題,主要有以下幾種寫法:

關鍵字synchronized可以保證在他同一時刻,隻有一個線程可以執行某一個方法,或者某一個代碼塊。

同步getInstance()方法是處理多線程最直接的做法。隻要把getInstance()變成同步(synchronized)方法,就可以解決并發問題了。

但是,同步的效率低,會降低性能。隻有第一次執行此方法的時候,才真正需要同步。也就是說,一旦設定好uniqueInstance變量,就不再需要同步這個方法了。之後每次調用這個方法,同步都是一種累贅。同步getInstance()方法既簡單又有效。如果說對性能要求不高,這樣就可以滿足要求。

之前的實作采用的是懶加載方式,也就是說,當真正用到的時候才會建立;如果沒被使用到,就一直不會建立。

懶加載方式在第一次使用的時候, 需要進行初始化操作,可能會比較耗時。

如果确定一個對象一定會使用的話,可以采用“急切”地執行個體化,事先準備好這個對象,需要的時候直接使用就行了。這種方式也叫做餓漢模式。具體代碼:

餓漢模式是如何保證線程安全的?

餓漢模式中的靜态變量是随着類加載時被初始化的。static關鍵字保證了該變量是類級别的,也就是說這個類被加載的時候被初始化一次。注意與對象級别和方法級别進行區分。

因為類的初始化是由類加載器完成的,這其實是利用了類加載器的線程安全機制。類加載器的loadClass方法在加載類的時候使用了synchronized關鍵字。也正是因為這樣, 除非被重寫,這個方法預設在整個裝載過程中都是同步的(線程安全的)。

殺雞用牛刀。實作單例模式可以利用雙重檢查加鎖(double-checked locking),首先檢查是否執行個體已經建立了,如果尚未建立,“才”進行同步。這樣,隻有第一次會同步。

如果性能是關注的重點,雙重檢查加鎖可以大幅減少getInstance()的時間消耗成本。

在Java 1.5發行版本之前,雙重檢查模式的功能很不穩定,因為volatile修飾符的語義不夠強,難以支援它。Java 1.5發行版本中引入的記憶體模式解決了這個問題,如今,雙重檢查模式是延遲初始化的一個執行個體域的方法。

為什麼要進行雙重檢查?隻檢查一次不行嗎?

解答:隻檢查一次不行。隻檢查一次的代碼如下:

當兩個線程同時判斷uniqueInstance == null的時候,都會去獲得Singleton.class的鎖對象,由于兩個線程擁有的鎖對象是同一個Singleton.class,兩個線程先後執行,也就是兩個線程都會進入同步代碼塊建立一個新的對象,造成傳回的uniqueInstance 并不是唯一的,這樣也就不符合單例模式了。

從Java 1.5發行版本起,實作Singleton隻需要編寫一個包含單個元素的枚舉類型:

使用枚舉實作單例的方法雖然還沒有廣泛采用,但是單元素的枚舉類型已經成為實作Singleton的最佳方法。注意:如果Singleton必須拓展一個超類,而不是擴充Enum的時候,則不宜使用這個方法。

Eric Freeman;ElElisabeth Freeman.HeadFirst設計模式[M]. 北京:中國電力出版社, 2007.

Joshua Bloch.Effective Java中文版(原書第3版)[M]. 北京:機械工業出版社, 2018.

漫話:如何給女朋友解釋什麼是單例模式?

由于部落客也是在攀登的路上,文中可能存在不當之處,歡迎各位多指教! 如果文章對您有用,那麼請點個”推薦“,以資鼓勵!

上一篇: PXC使用介紹
下一篇: Python之dict