破壞單例模式的原因

2021-09-24 12:12:00 字數 4156 閱讀 8301

大家都知道單例模式,單例模式(singleton pattern)是指確保乙個類在任何情況下都絕對只有乙個例項,並提供乙個全域性訪問點。單例模式是建立型模式。具體做法是將構造器私有化,不讓其他類呼叫構造器,即其他類不能通過new的方式建立物件。但是反射序列化轉殖等操作都有可能會破壞單利模式。

現在看乙個問題,建立乙個類的物件有幾種方式呢?

由轉殖我們可以想到原型模式,原型模式就是**根據給定某個類的物件(稱為原型),複製出該類的另乙個物件,複製的物件與原型物件有著相同的變數值。**我們可以直接手動去new乙個新的物件,然後set原物件的屬性值到新物件的屬性中,也可以直接通過重寫object類中的clone方法。

就是通過clone方法實現物件的建立的,clone方法是object類中protected方法。我們知道,protected方法子類直接呼叫,哪怕不是同一包下也能呼叫。

但是object類中的clone方法特殊,不能夠讓子類直接呼叫。一般都都是子類重寫該方法,並且子類需要實現cloneable介面,這樣才可以對該類進行轉殖,如果子類不實現cloneable介面,那麼在呼叫clone方法會丟擲clonenotsupportedexception異常

public

class

person

implements

cloneable

//省略get,set方法

}/**

* 淺轉殖。

* @throws exception

*/@test

public

void

test01()

throws exception

//執行結果如下:

person

person

如果是這樣的話,那麼上面說的單例模式失效了?當然答案是否定,某乙個物件直接呼叫clone方法,會丟擲異常,即並不能成功轉殖乙個物件。呼叫該方法時,必須實現乙個cloneable 介面。這也就是原型模式的實現方式。還有即如果該類實現了cloneable介面,儘管建構函式是私有的,他也可以建立乙個物件。即clone方法是不會呼叫建構函式的,他是直接從記憶體中copy記憶體區域的。所以單例模式的類是不可以實現cloneable介面的

/**

@author 青冰白夜

最簡單的單例模式,這裡就先不考慮執行緒安全問題,主要是描述下單例破壞的情況

*/public

class

singleton

private

static singleton instance=null;

public

static singleton getinstance()

return instance;}}

/**測試類

*/@test

public

void

test02()

throws exception

//執行結果如下:

private

singleton()

true

private

singleton()

false

單例模式的構造方法除了加上 private 以外,沒有做任何處理。如果我們使用反射來呼叫其構造方法,然後,再呼叫 getinstance()方法,應該就會兩個不同的例項。由上面的測試可知,即便將構造器私有化了,還是會被反射呼叫,反射就這樣破壞了單例。下面介紹解決方法。

/**

* @author zhouxingcan

*/public

class

singleton

system.out.

println

("private singleton()");

}public

static singleton getinstance()

return instance;

}}

上述**考慮的是,在構造器中,判斷例項是否為null,若不是就直接丟擲異常。在這裡很多人可能會說這樣看似可以防止反射呼叫私有構造,其實還有一點需要注意,反射照樣可以修改私有變數的值,即可以修改例項變數instance為null,這樣照樣可以呼叫私有構造器了。

這麼說是沒有錯的,但是呢,正常情況下,外界是不知道該單例的例項變數的名字。我也查閱了很多資料,也都沒有提到這個問題,所有在此可以先忽略。如果後續發現更好的方式,會持續更新部落格。

@test

public

void

test02()

throws exception

//執行結果如下:

private

singleton()

true

com.zxc.pattern.原型模式.singleton@621be5d1

--com.zxc.pattern.原型模式.singleton@621be5d1

private

singleton()

false

/**

使用內部類的方式,

*/public

class

lazyinnerclasssingleton

}//每乙個關鍵字都不是多餘的

//static 是為了使單例的空間共享

//保證這個方法不會被重寫,過載

public

static

final lazyinnerclasssingleton getinstance()

//預設不載入

private

static

class

lazyholder

}

這種內部類實現單例的方式,是絕對執行緒安全的,既可以防止高併發情況下執行緒安全問題,又可以實現保護單例。

當我們將乙個單例物件建立好,有時候需要將物件序列化然後寫入到物理記憶體,下次使用時再從物理記憶體中讀取到物件,反序列化轉化為jvm記憶體物件。反序列化後的物件會重新分配jvm記憶體,即重新建立。那如果序列化的目標的物件為單例物件,就違背了單例模式的初衷,相當於破壞了單例。

來看一段**:

/**

需要序列化的類

*/public

class

singletonserializable

implements

serializable

public

static singletonserializable getinstance()

return instance;}}

/**測試類

*/public

class

singletondemo

}//輸出結果如下:

單例模式.singletonserializable@330bedb4

單例模式.singletonserializable@16b98e56

}

執行結果中,可以看出,反序列化後的物件和手動建立的物件是不一致的,例項化了兩次,違背了單例的設計初衷。那麼,我們如何保證序列化的情況下也能夠實現單例?其實很簡單,只需要增加 readresolve()方法即可。來看優化**:

public

class

singletonserializable

implements

serializable

private

singletonserializable()

public

static singletonserializable getinstance()

return instance;

}private object readresolve()

}

再看執行結果

com.zxc.pattern.單例模式.singletonserializable@45ee12a7

com.zxc.pattern.單例模式.singletonserializable@45ee12a7

兩個是同乙個物件,說明可行。看起來簡單,但是為什麼呢?這個等後續更新註冊式單例,以及jdk中objectoutputstream原始碼分析才能深入理解原因,具體後續分享。

單例模式,解決單例破壞。

破壞單例模式的三種方法 執行緒安全情況下 單例模式有 3 個特點 單例類只有乙個例項物件 該單例物件必須由單例類自行建立 單例類對外提供乙個訪問該單例的全域性訪問點。單例模式的優點和缺點 單例模式的優點 單例模式可以保證記憶體裡只有乙個例項,減少了記憶體的開銷。可以避免對資源的多重占用。單例模式設定...

附 單例模式的破壞

序列化物件對單例模式的破壞與恢復 首先這是乙個餓漢式的單例物件構建方式,一般情況下獲取到的都是同乙個單例物件 但是當序列化寫入本地再讀入記憶體時,會重新建立乙個單例物件 為什麼會在讀入序列化後的物件時會讓單例模式失效呢?這兒從readobject 方法入手 進入這個方法後 private 進入pri...

單例模式的破壞及任何防止被破壞

常用的單例模式有懶漢式 餓漢式兩種情況。實際的應用場景也是很常見,好比如資料庫連線池的設計,還有windows的task manager 任務管理器 等。所謂單例模式就是,某乙個類只能有乙個例項,實現的核心就是將類的建構函式私有化,只能由該類建立物件,其他物件就不能呼叫該類的建構函式,即不能建立物件...