現(xiàn)方式與最佳實(shí)踐)
1. 單例模式的核心價(jià)值與應(yīng)用場(chǎng)景單例模式作為創(chuàng)建型設(shè)計(jì)模式的代表在Java開(kāi)發(fā)中有著不可替代的地位。它的核心價(jià)值在于確保一個(gè)類(lèi)在任何情況下都只有一個(gè)實(shí)例存在并提供一個(gè)全局訪問(wèn)點(diǎn)。這種特性在需要嚴(yán)格控制實(shí)例數(shù)量的場(chǎng)景下尤為重要。在實(shí)際開(kāi)發(fā)中單例模式的典型應(yīng)用場(chǎng)景包括配置管理類(lèi)整個(gè)系統(tǒng)只需要一個(gè)配置實(shí)例數(shù)據(jù)庫(kù)連接池避免重復(fù)創(chuàng)建連接日志記錄器統(tǒng)一管理日志輸出線程池控制線程資源分配緩存系統(tǒng)維護(hù)全局緩存狀態(tài)注意單例模式雖然實(shí)用但濫用會(huì)導(dǎo)致代碼耦合度增加測(cè)試?yán)щy。應(yīng)當(dāng)僅在確實(shí)需要全局唯一實(shí)例的場(chǎng)景下使用。2. 餓漢式單例的實(shí)現(xiàn)與特性分析2.1 經(jīng)典餓漢式實(shí)現(xiàn)public class EagerSingleton { // 類(lèi)加載時(shí)就初始化 private static final EagerSingleton instance new EagerSingleton(); // 私有構(gòu)造方法 private EagerSingleton() {} // 全局訪問(wèn)點(diǎn) public static EagerSingleton getInstance() { return instance; } }餓漢式的核心特點(diǎn)是在類(lèi)加載時(shí)就完成了實(shí)例化這種實(shí)現(xiàn)方式具有以下優(yōu)勢(shì)線程安全由JVM保證類(lèi)加載過(guò)程的線程安全性實(shí)現(xiàn)簡(jiǎn)單代碼直觀易懂性能高效獲取實(shí)例時(shí)無(wú)需同步判斷2.2 餓漢式的適用場(chǎng)景與限制餓漢式最適合以下場(chǎng)景實(shí)例創(chuàng)建開(kāi)銷(xiāo)不大程序運(yùn)行期間一定會(huì)用到該實(shí)例對(duì)性能要求較高的場(chǎng)景但需要注意如果實(shí)例初始化耗時(shí)較長(zhǎng)會(huì)拖慢應(yīng)用啟動(dòng)速度即使從未使用該實(shí)例也會(huì)占用內(nèi)存空間不支持延遲加載Lazy Initialization3. 懶漢式單例的多種實(shí)現(xiàn)方案3.1 基礎(chǔ)懶漢式實(shí)現(xiàn)線程不安全public class LazySingleton { private static LazySingleton instance; private LazySingleton() {} public static LazySingleton getInstance() { if (instance null) { instance new LazySingleton(); } return instance; } }這種實(shí)現(xiàn)方式雖然實(shí)現(xiàn)了延遲加載但在多線程環(huán)境下會(huì)出現(xiàn)競(jìng)態(tài)條件可能導(dǎo)致創(chuàng)建多個(gè)實(shí)例。3.2 同步方法實(shí)現(xiàn)的線程安全懶漢式public class SynchronizedLazySingleton { private static SynchronizedLazySingleton instance; private SynchronizedLazySingleton() {} public static synchronized SynchronizedLazySingleton getInstance() { if (instance null) { instance new SynchronizedLazySingleton(); } return instance; } }通過(guò)給getInstance()方法添加synchronized關(guān)鍵字解決了線程安全問(wèn)題但帶來(lái)了性能開(kāi)銷(xiāo)每次獲取實(shí)例都需要獲取鎖實(shí)際上只有第一次創(chuàng)建實(shí)例時(shí)需要同步3.3 雙重檢查鎖定DCL實(shí)現(xiàn)public class DCLSingleton { private volatile static DCLSingleton instance; private DCLSingleton() {} public static DCLSingleton getInstance() { if (instance null) { synchronized (DCLSingleton.class) { if (instance null) { instance new DCLSingleton(); } } } return instance; } }雙重檢查鎖定模式是懶漢式的優(yōu)化方案第一次檢查避免不必要的同步同步塊內(nèi)再次檢查確保唯一性使用volatile防止指令重排序關(guān)鍵點(diǎn)必須使用volatile關(guān)鍵字否則可能獲取到未初始化完成的對(duì)象。4. 靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)方案public class InnerClassSingleton { private InnerClassSingleton() {} private static class SingletonHolder { private static final InnerClassSingleton INSTANCE new InnerClassSingleton(); } public static InnerClassSingleton getInstance() { return SingletonHolder.INSTANCE; } }靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)結(jié)合了餓漢式和懶漢式的優(yōu)點(diǎn)線程安全由JVM保證類(lèi)加載的線程安全延遲加載只有在調(diào)用getInstance()時(shí)才會(huì)加載SingletonHolder類(lèi)無(wú)同步開(kāi)銷(xiāo)不需要額外的同步措施這種實(shí)現(xiàn)方式是目前最推薦的單例實(shí)現(xiàn)方案之一。5. 枚舉實(shí)現(xiàn)單例模式public enum EnumSingleton { INSTANCE; public void doSomething() { // 業(yè)務(wù)方法 } }枚舉單例是《Effective Java》作者Joshua Bloch推薦的方式具有以下優(yōu)勢(shì)絕對(duì)防止多次實(shí)例化自動(dòng)支持序列化機(jī)制線程安全代碼簡(jiǎn)潔枚舉實(shí)現(xiàn)的單例在面對(duì)反射攻擊和序列化/反序列化時(shí)依然能保持單例特性這是其他實(shí)現(xiàn)方式難以做到的。6. 性能對(duì)比與選型建議6.1 各種實(shí)現(xiàn)方式的性能對(duì)比實(shí)現(xiàn)方式線程安全延遲加載性能防反射防序列化餓漢式是否高否否同步懶漢式是是低否否DCL是是中否否靜態(tài)內(nèi)部類(lèi)是是高否否枚舉是否高是是6.2 實(shí)際項(xiàng)目中的選型建議如果確定實(shí)例一定會(huì)被使用且對(duì)啟動(dòng)性能不敏感 → 選擇餓漢式需要延遲加載且對(duì)性能有要求 → 靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)需要防御反射和序列化攻擊 → 枚舉實(shí)現(xiàn)JDK版本較低1.5且需要延遲加載 → 同步懶漢式需要極致的性能且能確保線程安全 → DCL實(shí)現(xiàn)7. 單例模式的常見(jiàn)問(wèn)題與解決方案7.1 反射攻擊與防御通過(guò)反射可以調(diào)用私有構(gòu)造方法創(chuàng)建新實(shí)例破壞單例。防御方法public class ReflectionProofSingleton { private static final ReflectionProofSingleton instance new ReflectionProofSingleton(); private ReflectionProofSingleton() { if (instance ! null) { throw new RuntimeException(Use getInstance() method to get the single instance); } } public static ReflectionProofSingleton getInstance() { return instance; } }7.2 序列化問(wèn)題與解決反序列化時(shí)會(huì)創(chuàng)建新實(shí)例破壞單例。解決方法public class SerializableSingleton implements Serializable { private static final long serialVersionUID 1L; private static final SerializableSingleton instance new SerializableSingleton(); private SerializableSingleton() {} public static SerializableSingleton getInstance() { return instance; } protected Object readResolve() { return instance; } }7.3 多類(lèi)加載器環(huán)境下的單例不同類(lèi)加載器加載的類(lèi)被視為不同的類(lèi)可能導(dǎo)致單例失效。解決方案指定類(lèi)加載器使用上下文類(lèi)加載器避免多類(lèi)加載器加載單例類(lèi)8. 單例模式在框架中的應(yīng)用實(shí)例8.1 Spring框架中的單例Spring默認(rèn)的bean作用域就是單例但與設(shè)計(jì)模式中的單例有所不同Spring單例是容器級(jí)別的一個(gè)容器只有一個(gè)實(shí)例傳統(tǒng)單例是JVM級(jí)別的一個(gè)JVM只有一個(gè)實(shí)例8.2 Android中的單例應(yīng)用在Android開(kāi)發(fā)中單例常用于全局配置管理系統(tǒng)服務(wù)訪問(wèn)資源池管理但需要注意避免在Activity中直接持有單例引用可能導(dǎo)致內(nèi)存泄漏考慮應(yīng)用進(jìn)程被殺死后單例狀態(tài)恢復(fù)問(wèn)題8.3 數(shù)據(jù)庫(kù)連接池的實(shí)現(xiàn)大多數(shù)數(shù)據(jù)庫(kù)連接池都采用單例模式管理public class ConnectionPool { private static final int MAX_POOL_SIZE 10; private static ConnectionPool instance; private ListConnection connections; private ConnectionPool() { // 初始化連接池 } public static synchronized ConnectionPool getInstance() { if (instance null) { instance new ConnectionPool(); } return instance; } public Connection getConnection() { // 從池中獲取連接 } public void releaseConnection(Connection conn) { // 釋放連接回池中 } }9. 單例模式的替代方案在某些場(chǎng)景下可以考慮以下替代方案依賴(lài)注入通過(guò)框架如Spring管理實(shí)例生命周期靜態(tài)工具類(lèi)如果不需要維護(hù)狀態(tài)上下文對(duì)象通過(guò)參數(shù)傳遞共享資源服務(wù)定位器模式集中管理服務(wù)實(shí)例選擇替代方案時(shí)需要考慮代碼的可測(cè)試性耦合度靈活性需求狀態(tài)維護(hù)需求10. 單例模式的最佳實(shí)踐根據(jù)多年項(xiàng)目經(jīng)驗(yàn)總結(jié)以下實(shí)踐建議優(yōu)先考慮枚舉或靜態(tài)內(nèi)部類(lèi)實(shí)現(xiàn)如果必須使用DCL確保正確使用volatile為單例類(lèi)編寫(xiě)清晰的文檔說(shuō)明考慮使用工廠方法封裝單例創(chuàng)建過(guò)程在分布式系統(tǒng)中單例需要特殊處理如使用分布式鎖單例對(duì)象應(yīng)該是無(wú)狀態(tài)的或有完善的狀態(tài)管理機(jī)制避免在單例中執(zhí)行耗時(shí)操作影響系統(tǒng)性能為單例設(shè)計(jì)合理的銷(xiāo)毀機(jī)制如連接池的關(guān)閉方法在大型項(xiàng)目中我曾經(jīng)遇到過(guò)因?yàn)椴划?dāng)使用單例導(dǎo)致的內(nèi)存泄漏問(wèn)題。后來(lái)通過(guò)引入弱引用和定期清理機(jī)制解決了這個(gè)問(wèn)題。關(guān)鍵是要記住單例雖然方便但也需要精心設(shè)計(jì)和管理。