簡介
單例模式:一個類有且僅有一個實例,該類負責創建自己的對象,同時確保只有一個對象被創建。
特點:類構造器私有、持有自己實例、對外提供獲取實例的靜態方法。
單例模式的實現方式
餓漢式
類被加載時,就會實例化一個對象并交給自己的引用,供系統使用;而且,由于這個類在整個生命周期中只會被加載一次,因此只會創建一個實例,即能夠充分保證單例。
案例:
public class Singleton {private static Singleton instance = new Singleton();private Singleton() { }public static Singleton getInstance() {return instance;}
}
餓漢式比較耗費資源,因為它創建單例的時間過早,它是在類被加載的時候創建單例的
懶漢式加雙重檢查加鎖
餓漢式的優化,只有在獲取類的實例時才會創建實例。
案例:
public class SingleTon {// 使用volatile修飾,保證變量的可見性private static volatile SingleTon instance;public static SingleTon getInstance() {// 先檢查實例是否存在,如果不存在才進入下面的同步塊if (instance == null) {// 同步塊,線程安全的創建實例synchronized (SingleTon.class) {// 再次檢查實例是否存在,如果不存在才真的創建實例if (instance == null) {instance = new SingleTon();}}}return instance;}
}
餓漢式中的注意事項:
1、為什么類持有的自己的私有實例要用volatile修飾:為了保證指定變量的有序性和可見性。new一個對象的代碼 SingleTon instance = new SingleTon();
可以分解為3行偽代碼:
memory=allocate();// 分配內存 相當于c的malloc
ctorInstanc(memory) //初始化對象
instance=memory //設置instance指向剛分配的地址
上面的代碼在編譯器運行時,可能出現重排序,從 1-2-3 變為 1-3-2,在多線程環境下就會出現問題,使用 volatile 關鍵字會禁止這種重排序。
2、為什么要雙重鎖:如果只有一個鎖,很有可能兩個線程都通過了 if(instance == null)
的判斷,所以在進入同步代碼塊之后還需要再判斷一次
用靜態內部類來實現單例模式
案例:
public class SingleTon {private SingleTon2() { }// 用一個私有的靜態內部類來存儲外部類的實例,類只會被加載一次,保證單例。// 內部類只有在被調用時才會被加載,保證了延遲加載private static class SingleTonHolder {private static SingleTon2 instance = new SingleTon2();}public static SingleTon2 getInstance() {return SingleTonHolder.instance;}
}
破壞單例模式
破壞單例模式:序列化和反射可以破壞單例模式。
- 解決序列化破壞單例的問題:在類中添加readResolve方法,返回類中的實例,可以解決通過序列化破壞單例模式的問題;
- 解決反射破壞單例的問題:在構造方法中拋異常,可以解決通過反射破壞單例模式的問題
單例模式的使用案例
餓漢式單例模式的使用:jdk中的Runtime類,每個java程序中都只有一個Runtime實例,它代表java程序的運行環境
public class Runtime {// 類被加載時,就會實例化一個對象并交給自己的引用private static Runtime currentRuntime = new Runtime();// 返回單例對象的方法public static Runtime getRuntime() {return currentRuntime;}// 私有化的構造方法/** Don't let anyone else instantiate this class */private Runtime() {}
}