設計模式 讓我們一起破壞單例模式吧

2021-09-12 08:25:25 字數 3919 閱讀 5308

簡介

單例物件的類必須保證只有乙個例項存在,並提供乙個全域性訪問點。可以減少記憶體開銷,同時避免對資源的多重占用。連線池、執行緒池都是常見的單例模式。

實現乙個單例模式基本有兩種思路,一種是在初次使用時構建(懶漢式),一種是在類載入時構建(餓漢式)。這兩種思路中,該類的建構函式都必須定義為私有方法,這樣其他處的**就無法通過呼叫該類的建構函式來例項化該類的物件,只有通過該類提供的靜態方法來得到該類的唯一例項。下面我們嘗試實現一下。

單例模式的實現

第一種思路,例項在初次呼叫時構建。當我們呼叫獲取例項的方法getinstance時,如果類持有的引用不為空就返回這個引用,如果為空就建立該類的例項,並將該例項賦予該類持有的引用。

public

class

lazysingleton

public

static lazysingleton getinstance()

return instance;

}}

然而這種方式在多執行緒場景下是不安全的。如果當唯一例項尚未建立時,有兩個執行緒同時呼叫建立方法,那麼它們同時沒有檢測到唯一例項的存在,從而同時各自建立了乙個例項,這樣就有兩個例項被構造出來,從而違反了單例模式中例項唯一的原則。我們可以使用雙重檢查鎖來避免這個問題。

public

class

doublechecklazysingleton

public

static doublechecklazysingleton getinstance()

}}return instance;

}}

public

class

doublechecklazysingleton

public

static doublechecklazysingleton getinstance()

}}return instance;

}}

public

class

staticinnerclasssingleton

private

static

class

innerclass

public

static staticinnerclasssingleton getinstance()

}

另一種思路,在類載入時構建例項。這種方案有可能會造成資源浪費,比如這個物件你自始至終就沒有使用過。但是簡單。對於類載入時即初始化的例項,我比較喜歡放在static**塊中,這樣比較一目了然。

public

class

hungrysingleton

private

hungrysingleton()

public

static hungrysingleton getinstance()

}

隱患

你以為搞定了執行緒安全問題,單例模式就沒有其他隱患了麼,too young。單例模式作為經常被cue的設計模式,還是有很多料可以挖的。想要破壞我們上面實現的單例模式,序列化可以,反射也可以。

我們用比較簡單的餓漢式作為例子,為了測試序列化破壞單例,hungrysingleton需要先實現serializable介面。然後就開始破壞吧。

public

class

test

}

執行後控制台輸出結果為:

instance: com.anne.design.pattern.creational.singleton.hungrysingleton@3764951d

newinstance: com.anne.design.pattern.creational.singleton.hungrysingleton@34c45dca

instance == newinstance: false

實驗表明,通過序列化和反序列化,我們的單例模式遭到了破壞,現在系統裡有兩個hungrysingleton例項了,違背了單例模式的初衷。哭唧唧。那麼怎麼辦呢?

操作很簡單,只需要在hungrysingleton類中增加readresolve()方法即可。

public

class

hungrysingleton

implements

serializable

private

hungrysingleton()

public

static hungrysingleton getinstance()

private object readresolve()

}

在執行上面的測試方法,我們就可以得到

instance: com.anne.design.pattern.creational.singleton.hungrysingleton@3764951d

newinstance: com.anne.design.pattern.creational.singleton.hungrysingleton@3764951d

instance == newinstance: true

這是為什麼呢?readresolve()為何有如此功能?答案在objectinputstream的readobject()方法中,其中一段:

if

(obj != null && handles.

lookupexception

(passhandle)

== null && desc.

hasreadresolvemethod()

)}return obj;

hasreadresolvemethod就是看這個物件的類中,是否有readresolve(),如果有就通過反射呼叫這個方法,並返回這個方法指定的物件。

之前說了,破壞單例模式這件事,序列化反序列化可以,反射也可以。因為我們可以利用反射改變構造方法的可見性呀。

public

class

test

}

輸出的結果為:

instance: com.anne.design.pattern.creational.singleton.hungrysingleton@2b193f2d

newinstance: com.anne.design.pattern.creational.singleton.hungrysingleton@355da254

instance == newinstance: false

對於這種情況,我們可以在hungrysingleton的構造方法中增加防禦機制。

public

class

hungrysingleton

implements

serializable

private

hungrysingleton()

}public

static hungrysingleton getinstance()

private object readresolve()

}

再測試,就會丟擲異常,不會出現重複建立的情況。這個方案同樣也實用於通過靜態內部類實現的懶漢式。而對於一般的懶漢式,反射破壞防不勝防,因為無論用多麼複雜的邏輯去判斷instance已經建立,我們都可以用反射修改其中的關鍵值。

總結單例模式看起來簡單,可實際實現的過程,道阻且長。不過只要注意以下幾點,就會思路清晰啦。

必須有乙個private構造器。

注意執行緒安全問題。

注意序列化和反序列化的安全問題。

注意反射攻擊。

單例設計模式 序列化破壞單例模式?

1 問題猜想,假如將乙個物件通過序列化放到乙個檔案後,再取出來看是否與本身相等?public class hungrysingleton implements serializable private hungrysingleton public static hungrysingleton get...

摩卡,讓我們一起成長

本人加入摩卡倒頗有些緣分 在剛籌畫成立天津研發中心時,就差點成為研發中心的一員,但由於種種原因未能成行 經過一年多的等待,最終還是走進了摩卡,成為一名上海本土員工。時間如梭,加入摩卡轉眼三年了,我能很清楚地感覺到自己的成長與進步,同時也目睹了公司快速健康發展與壯大的過程,能成為摩卡人的一分子感到榮幸...

摩卡,讓我們一起成長

本人加入摩卡倒頗有些緣分 在剛籌畫成立天津研發中心時,就差點成為研發中心的一員,但由於種種原因未能成行 經過一年多的等待,最終還是走進了摩卡,成為一名上海本土員工。時間如梭,加入摩卡轉眼三年了,我能很清楚地感覺到自己的成長與進步,同時也目睹了公司快速健康發展與壯大的過程,能成為摩卡人的一分子感到榮幸...