單例模式是設計模式中使用最多的模式之一,它是一種對象的建立模式,用於產生一個對象的實例,確保系統中一個類只有一個實例。在Java語言中,單例模式有如下好處:java
所以對於系統的關鍵組件和頻繁使用的對象,能夠設計爲單例模式,減小系統的開銷,提升系統性能。設計模式
單例模式的參與者不多,只有單例類和使用者,關係表以下:多線程
角色 | 做用 |
---|---|
單例類 | 提供單例的工廠,返回單例 |
使用者 | 獲取並使用單例 |
類圖以下:函數
單例模式的核心在於經過一個接口返回惟一實例化對象,簡單實現以下:性能
/** * 單例模式--餓漢(沒法作到延時加載) */
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
/** * 私有構造方法 */
private HungrySingleton(){
System.out.println("建立了對象");
}
/** * 對外暴露的獲取惟一實例的接口 * @return */
public static HungrySingleton getInstance(){
return instance;
}
/** * 序列化,反序列化保證單例 * @return */
private Object readResolve(){
return instance;
}
}
複製代碼
原理:單例類必須私有化構造方法,保證不會在系統其餘地方調用,其次instance成員變量和getInstance()方法必須是static修飾的。優化
注意:單例模式的這種實現方式很是簡單,十分可靠,惟一不足是沒法作到延時加載。假設單例的建立過程十分緩慢,因爲instance的成員變量是由static修飾的,在JVM加載單例類的時候,單例對象就會存在,若是單例類還在系統中扮演別的角色,那麼系統中任何使用單例類的地方都會初始化這個單例對象,而無論是否被用到。spa
爲了解決上述問題,並提升系統在相關函數調用的反應速度,就須要加入延時加載機制,懶漢模式。線程
/** * 單例模式--懶漢(效率低,延時加載) */
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
System.out.println("LazySingleton is create");
}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
return new LazySingleton();
}
return instance;
}
}
複製代碼
原理:首先,靜態成員變量instance賦值爲null,確保系統啓動時沒有額外負載,其次在getInstance()方法中判斷當前單例instance對象是否存在,存在則返回,不存在建立單例對象。設計
注意:getInstance()必須是線程同步的,不然在多線程條件下,當線程1新建單例完成賦值前,線程2可能判斷instance爲null,線程2也建立了單例對象,致使多個實例被建立,所以同步關鍵字是必須的。使用上述單例模式的實現方式,雖然實現了延時加載,可是和第一種實現(餓漢)相比,引入了同步關鍵字,所以在多線程場景下,加載速度遠遠大於第一種實現方式,影響系統性能。code
繼續改進,建立內部類:
/** * 使用內部類來維護單例的實例,當StaticSingleton被加載時候,內部類並無被初始化 * (instance並無被初始化),調用getInstance()纔會被初始化。 */
public class StaticSingleton {
private StaticSingleton(){
System.out.println("StaticSingleton is create");
}
/** * 內部類,建立單例對象 */
private static class StaticSingleHolder{
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance(){
return StaticSingleHolder.instance;
}
}
複製代碼
原理:在這個實現中,使用內部類來維護單例實例,當StaticSingleton被加載的時候,內部類沒有被初始化,能夠確保StaticSingleton加載到JVM中,不會初始化單例類,當調用getInstance()時纔會加載StaticSingleHolder,初始化instance對象,同時因爲實例的創建是在類加載時完成的,對線程友好,getInstance()不須要使用同步關鍵字。
注意:使用內部類實現的單例,既能夠實現延時加載也避免使用同步關鍵字,是比較完善的實現。可是若是經過反射機制強行調用私有構造方法,就會生成多個單例。同時序列化和反序列化可能破壞單例(餓漢代碼readResolve()方法),場景很少見,若是存在,多加註意。
[^《Java性能程序優化 讓你的Java程序更快、更穩定》 葛一鳴]