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