單例模式的十種寫法,你會幾個?(重寫版)

這篇文章是今天重寫的,開會搞了一下午,另一篇文章寫了一半,等晚上寫完明天發。最近在使用一個科學的方法研究一個有意思的事,如何科學化的判斷你的另一半是否也愛你。廢話不多說了,開始一下今天的文章。

單例模式是一種最常見的設計模式,寫法也比較多,在這篇文章裡面主要是對單例模式的各種寫法進行一個介紹。面試的時候會對其中兩三種進行體會,而且我還遇到了口述單例模式的例子。重要性就不言而喻了吧。


單例模式的十種寫法,你會幾個?(重寫版)


一、單例模式的介紹

單例模式的重要點在於兩個,一個是在哪些地方使用到了單例模式,一個是單例模式如何寫。之前只考慮到了如何寫,但是哪些地方使用到了,表述的不是很清楚。這一次我找了幾個實際例子。

概念:單例模式確保某個類只有一個實例。

有一個通俗的理解,那就是在古代,全國就一個皇帝。如何確保一個皇帝?這就是單例模式。

先看如何寫,然後再看在哪用。

二、單例模式的各種寫法

1、懶漢式:基本寫法

懶漢式就是什麼時候用,什麼時候創建類的實例。

<code>

public

class

Singleton

{

private

Singleton

(

)

{}

private

static

Singleton single=

null

;

public

static

Singleton

getInstance

(

)

{

if

(single ==

null

) { single =

new

Singleton(); }

return

single; } } /<code>

特點:

(1)線程不安全(併發時可能出現多個單例)

(2)構造方法為private,限定了外部只能從getInstance去獲取單例

(3)使用static關鍵字,表明全局只有一份節約了資源,但是如果單例對象比較複雜,new時就比較耗時間。這一點要注意。

上面最主要的缺點就是線程不安全,因此想要解決這個問題,只需要對方法加鎖即可。

2、懶漢式:使用synchronized 同步

<code>

public

class

Singleton

{

private

static

Singleton instance;

private

Singleton

()

{}

public

static

synchronized

Singleton

getInstance

()

{

if

(instance ==

null

) { instance =

new

Singleton(); }

return

instance; } } /<code>

特點:

(1)此時線程安全

(2)因為加了鎖,此時如果單例對象複雜,不僅耗內存,而且new的時間更長,效率更低。

但是上面這種效率不高還有另外一個原因。一個線程過來想要創建單例,首先要進行synchronized鎖判斷,接下來判斷單例是否為空,如果為空,那就創建單例。

既然上面每個線程過來都需要鎖判斷和單例是否為空的判斷,這樣做有點耗時,畢竟鎖判斷是比較耗時的。

3、懶漢式:雙重檢查鎖定

為了解決上面的這個問題,才有了雙重檢查鎖定。

<code>

public

class

Singleton

{

private

static

Singleton instance;

private

Singleton

()

{}

public

static

Singleton

getInstance

()

{

if

(singleton ==

null

) {

synchronized

(Singleton

.

class

)

{

if

(singleton ==

null

) { singleton =

new

Singleton(); } } }

return

singleton; } /<code>

特點:

(1)線程安全

(2)耗內存,而且單例對象比較複雜,比較耗時,但是對單重鎖效率提升不少。

此時為了減少鎖的判斷量,只需要對單例進行判斷即可,如果不為空直接返回,如果為空,那就創建新的單例。

4、餓漢式:基本寫法(instance為private)

惡漢式是一開始就先建好,用的時候直接返回即可。

<code>

public

class

Singleton

{

private

Singleton

()

{}

private

static

final

Singleton instance =

new

Singleton();

public

static

Singleton

getInstance

()

{

return

instance; } } /<code>

特點:

(1)線程安全(因為提前創建了,所以是天生的線程安全)

(2)單例對象修飾為private,只能通過getInstance獲取。

此時單例因為有static修飾,因此在類加載的時候就會初始化,這對應用的啟動會造成一定程度的影響。

5、餓漢式:基本寫法(instance為public)

<code>

public

class

Singleton

{

public

static

final

Singleton instance =

new

Singleton();

private

Singleton

()

{} } /<code>

和上面沒什麼區別。

6、餓漢式:靜態代碼塊

<code>

public

class

Singleton

{

private

Singleton instance =

null

;

private

Singleton

(

)

{}

static

{ instance =

new

Singleton(); }

public

static

Singleton

getInstance

(

)

{

return

this

.instance; } } /<code>

特點:

(1)線程安全

(2)類初始化時實例化 instance

7、靜態內部類

<code>

public

class

Singleton

{

private

static

class

InstanceHolder

{

private

static

final

Singleton INSTANCE =

new

Singleton(); }

private

Singleton

()

{}

public

static

final

Singleton

getInstance

()

{

return

InstanceHolder.INSTANCE; } } /<code>

特點:

(1)線程安全

(2)效率高,避免了synchronized帶來的性能影響

這種就好很多。很多大佬都推薦。

8、枚舉式

<code>

public

enum

Singleton { INSTANCE;

public

void

getInstance

(

)

{ System.

out

.println(

"Do whatever you want"

); } } /<code>

特點:

(1)線程安全(枚舉類型默認就是安全的)

(2)避免反序列化破壞單例

枚舉類型更好,但是枚舉類型會造成更多的內存消耗。枚舉會比使用靜態變量多消耗兩倍的內存,如果是Android應用,儘量避免。原因的話,是因為枚舉類型會在編譯時轉化為一個類,會涉及很多複雜的操作,這裡就先不逼逼了。

9、CAS方式

<code>

public

class

Singleton

{

private

static

final AtomicReference INSTANCE =

new

AtomicReference ();

private

Singleton

(

)

{}

public

static

Singleton

getInstance

(

)

{

for

(;;) { Singleton instance = INSTANCE.

get

();

if

(instance !=

null

) {

return

instance; } instance =

new

Singleton();

if

(INSTANCE.compareAndSet(

null

, instance)) {

return

instance; } } } } /<code>

特點:

(1)優點:不需要使用傳統的鎖機制來保證線程安全,CAS 是一種基於忙等待的算法,依賴底層硬件的實現,相對於鎖它沒有線程切換和阻塞的額外消耗,可以支持較大的並行度。

如果不理解CAS的話,可以看這篇文章:

java併發編程CAS機制原理分析(面試必問,學習中必會的一個知識點)

(2)缺點:如果忙等待一直執行不成功(一直在死循環中),會對 CPU 造成較大的執行開銷。而且,這種寫法如果有多個線程同時執行 singleton = new Singleton(); 也會比較耗費堆內存。

10、Lock機制

<code> 

public

class

Singleton

{

private

static

Singleton instance =

null

;

private

static

Lock

lock

=

new

ReentrantLock();

private

Singleton

(

)

{}

public

static

Singleton

getInstance

(

)

{

if

(instance ==

null

) {

lock

.

lock

();

if

(instance ==

null

) { instance =

new

Singleton(); }

lock

.unlock(); }

return

instance; } } /<code>

當然還有一些其他的實現單例的寫法,比如說登記式單例等等。

三、單例模式的使用

1.Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個windows task manager嗎?不信你自己試試看哦~

  1. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程中,回收站一直維護著僅有的一個實例。
  2. 網站的計數器,一般也是採用單例模式實現,否則難以同步。
  3. 應用程序的日誌應用,一般都何用單例模式實現,這一般是由於共享的日誌文件一直處於打開狀態,因為只能有一個實例去操作,否則內容不好追加。
  4. Web應用的配置對象的讀取,一般也應用單例模式,這個是由於配置文件是共享的資源。

6.數據庫連接池的設計一般也是採用單例模式,因為數據庫連接是一種數據庫資源。數據庫軟件系統中使用數據庫連接池,主要是節省打開或者關閉數據庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因為何用單例模式來維護,就可以大大降低這種損耗。

  1. 多線程的線程池的設計一般也是採用單例模式,這是由於線程池要方便對池中的線程進行控制。
  2. 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。

以上內容來源於簡書:我本來寫了鏈接,但是頭條等各個站點,不能包含,我會在評論區給出,尊重原創。

四、總結

單例模式的十種寫法,你會幾個?(重寫版)

有兩種場景可能導致非單例的情況

場景一:如果單例由不同的類加載器加載,那便有可能存在多個單例類的實例

場景二:如果 Singleton 實現了 java.io.Serializable 接口,那麼這個類的實例就可能被序列化和反序列化。

單例的寫法基本上就是這些,可能在不同的場景下使用不同的方式,對我來說,在後端更經常使用的就是枚舉類型,但是Android開發當中很少使用。


分享到:


相關文章: