做網(wǎng)站有哪個(gè)軟件好如何制作自己的網(wǎng)址
《Effective Java》中的第3條編程法則主要是針對(duì)在開發(fā)過程如何實(shí)現(xiàn)單例模式,作者 Joshua Bloch 在書中給出了3種單例模式的實(shí)現(xiàn)方式:私有構(gòu)造器和公有靜態(tài)域、私有構(gòu)造器和公有靜態(tài)方法、枚舉式。
什么是單例模式?
單例模式是一種設(shè)計(jì)模式,旨在確保一個(gè)類只有一個(gè)實(shí)例,其主要目的是控制類的實(shí)例化過程,避免創(chuàng)建多個(gè)對(duì)象實(shí)例;并提供一個(gè)全局訪問點(diǎn)來獲取該實(shí)例。這種模式適用于需要唯一對(duì)象來協(xié)調(diào)全局操作或資源管理的場(chǎng)景,例如配置管理器、線程池等。
單例實(shí)現(xiàn)方式
實(shí)現(xiàn)方式一:私有構(gòu)造器和公有靜態(tài)域(餓漢式單例模式)
通過私有構(gòu)造器和公有靜態(tài)域?qū)崿F(xiàn)單例模式是一種簡(jiǎn)單且直接的方法,這種實(shí)現(xiàn)通常被稱為餓漢式單例模式。
public class Singleton {// 單例對(duì)象,在類加載時(shí)創(chuàng)建public static final Singleton INSTANCE = new Singleton();// 私有構(gòu)造器,防止外部實(shí)例化private Singleton() {// 可以添加初始化代碼}// 其他方法public void doSomething() {// 實(shí)現(xiàn)具體的業(yè)務(wù)邏輯}
}
在這種實(shí)現(xiàn)中,通過將構(gòu)造器設(shè)為 private
,類外部無法直接創(chuàng)建實(shí)例,從而確保了單例模式的唯一性。而 static final
修飾符確保了在類加載階段就創(chuàng)建并初始化實(shí)例,并且實(shí)例一旦創(chuàng)建后不可變,這樣就進(jìn)一步保證了單例實(shí)例的唯一性和一致性。整體設(shè)計(jì)既簡(jiǎn)單又有效。
實(shí)現(xiàn)方式二:私有構(gòu)造器和公有靜態(tài)方法(懶漢式單例模式 & 雙重檢查鎖定)
私有構(gòu)造器和公有靜態(tài)方法的實(shí)現(xiàn)通常稱為懶漢式單例模式:
public class Singleton {private static Singleton instance;private Singleton() {// 私有構(gòu)造器}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
相比餓漢式單例模式實(shí)例的創(chuàng)建時(shí)機(jī)固定,懶漢式的實(shí)現(xiàn)將實(shí)例的初始化延遲到在第一次調(diào)用 getInstance
方法時(shí)創(chuàng)建,這樣可以避免在程序啟動(dòng)時(shí)就初始化實(shí)例,從而節(jié)省資源。
但是在懶漢式的實(shí)現(xiàn)中,使用了synchronized
關(guān)鍵字保證在多線程環(huán)境下對(duì) getInstance
方法的訪問是線程安全的。然而,這種實(shí)現(xiàn)方式的缺點(diǎn)是每次調(diào)用 getInstance
方法時(shí)都要進(jìn)行同步,這會(huì)帶來性能開銷。
幸運(yùn)的是,由于我們使用靜態(tài)工廠方法創(chuàng)建類的實(shí)例,那么我們就可以在方法種控制創(chuàng)建實(shí)例的時(shí)機(jī),即在懶漢式單例模式的基礎(chǔ)上進(jìn)行優(yōu)化,減少同步的開銷來提高效率:
- 在獲取實(shí)例時(shí),首先檢查實(shí)例是否已存在,如果已存在,則直接返回該實(shí)例。
- 如果實(shí)例不存在,再進(jìn)行同步檢查,以確保實(shí)例初始化的線程安全。
public class Singleton {// 使用 volatile 關(guān)鍵字確保線程安全private static volatile Singleton instance;// 私有構(gòu)造器防止外部實(shí)例化private Singleton() {// 私有構(gòu)造器}// 提供公共的靜態(tài)方法獲取實(shí)例public static Singleton getInstance() {if (instance == null) { // 第一次檢查synchronized (Singleton.class) {if (instance == null) { // 第二次檢查instance = new Singleton();}}}return instance;}
}
這種優(yōu)化在獲取實(shí)例時(shí)引入了兩次檢查,因此也被稱為雙重檢查鎖定:
- 第一次檢查:在同步塊外部進(jìn)行,以減少不必要的同步開銷。
- 第二次檢查:在同步塊內(nèi)部進(jìn)行,以確保實(shí)例的唯一性。
實(shí)現(xiàn)方式三:枚舉式
由于枚舉類型本身設(shè)計(jì)用于定義一組常量,因此實(shí)現(xiàn)單例模式時(shí),通常一個(gè)枚舉類型中只定義一個(gè)枚舉實(shí)例:
public enum Singleton {INSTANCE;public void doSomething() {// 實(shí)現(xiàn)具體功能}
}
餓漢式 VS 懶漢式 VS 枚舉式
餓漢式和懶漢式單例模式的實(shí)現(xiàn),在反序列化和反射攻擊可能會(huì)導(dǎo)致創(chuàng)建多個(gè)實(shí)例,破壞單例模式的唯一性。因此還需要添加以下處理:
-
實(shí)現(xiàn)
readResolve
方法,確保在反序列化過程中,獲取的對(duì)象仍然是單例實(shí)例,而不是新的實(shí)例:private Object readResolve() {return getInstance(); }
-
在構(gòu)造函數(shù)中增加檢查,防止通過反射創(chuàng)建多個(gè)實(shí)例:
private Singleton() {if (instance != null) {throw new RuntimeException("Use getInstance() method to get the single instance of this class.");} }
枚舉由于自身的特殊機(jī)制,使得枚舉式相比前兩者更適合單例模式得實(shí)現(xiàn):
- 天然的線程安全:Java 枚舉類型的實(shí)例在 JVM 加載時(shí)會(huì)創(chuàng)建一次且只創(chuàng)建一次,整個(gè)加載過程是線程安全的。因此,不需要額外的同步來保證線程安全。
- 防止反序列化攻擊:枚舉的實(shí)例由 JVM 管理,序列化和反序列化過程中保持唯一性。這意味著即使序列化和反序列化操作被惡意操控,也不會(huì)生成新的實(shí)例。
- 防止反射攻擊:枚舉類型的構(gòu)造函數(shù)是私有的,當(dāng)嘗試使用反射創(chuàng)建枚舉實(shí)例會(huì)拋出
IllegalArgumentException
異常,從而保護(hù)了單例的唯一性。
這也是 Joshua Bloch 在書中提到單元素的枚舉類型經(jīng)常成為實(shí)現(xiàn) Singleton 的最佳方法的原因。