1. 程式人生 > >單例模式,反射破環?

單例模式,反射破環?

> 餓漢式 ```java // 餓漢式單例 public class Hungry { //構造器私有 private Hungry(){ } // 一上來就把這個類載入了 private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } } ``` ```java // 餓漢式單例 public class Hungry { // 這4組資料非常耗記憶體資源,餓漢式一上來就把所有的記憶體裡面的東西全部載入進來了,就存在這個空間 // 但這個空間現在是沒有使用的,可能會造成浪費空間 private byte[] data1 = new byte[1024*1024]; private byte[] data2 = new byte[1024*1024]; private byte[] data3 = new byte[1024*1024]; private byte[] data4 = new byte[1024*1024]; //構造器私有 private Hungry(){ } // 一上來就把這個類載入了 private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } } ``` 餓漢式單例可能會造成浪費空間,所以想要用的時候再去建立這個物件,平時就先放在這個地方,於是就出現了懶漢式! > 懶漢式 ```java // 懶漢式單例 public class LazyMan { // 構造器私有 private LazyMan(){ } private static LazyMan lazyMan; public static LazyMan getInstance(){ if (lazyMan==null){ lazyMan = new LazyMan(); } return lazyMan; } } ``` 它是有問題的,單執行緒下確實單例ok,多執行緒併發就會出現問題! **測試** ```java // 懶漢式單例 public class LazyMan { // 構造器私有 private LazyMan(){ System.out.println(Thread.currentThread().getName()+":: ok"); } private static LazyMan lazyMan; public static LazyMan getInstance(){ if (lazyMan==null){ lazyMan = new LazyMan(); } return lazyMan; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } } ``` ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085229819-614760537.png) ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085216861-466747495.png) 發現單例有問題,每次結果可能都不一樣! **解決** ```java // 懶漢式單例 public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+":: ok"); } private static LazyMan lazyMan; // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式 public static LazyMan getInstance(){ if (lazyMan==null){ synchronized (LazyMan.class){ if (lazyMan==null){ lazyMan = new LazyMan(); } } } return lazyMan; } public static void main(String[] args) { for (int i = 0; i < 10 ; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } } ``` ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085200698-203464594.png) 但在極端情況下還是可能出現問題 ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085141489-348383799.png) 經歷三個步驟: 1、 分配記憶體空間 2、 執行構造方法,初始化物件 3、 把這個物件指向這個空間 **有可能會發生指令重排的操作!** 比如,期望它執行 123 ,但是它真實可能執行132,比如第一個A執行緒過來執行了132,先分配空間再吧這個空間佔用了,佔用之後再去執行構造方法,如果現在突然來了個B執行緒,由於A已經指向這個空間了,它會以為這個 lazyMan 不等於 null ,直接return ,此時lazyMan還沒有完成構造,所以必須避免這個問題! 必須加上`volatile` ```java // 懶漢式單例 public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+":: ok"); } // 避免指令重排 private volatile static LazyMan lazyMan; // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式 public static LazyMan getInstance(){ if (lazyMan==null){ synchronized (LazyMan.class){ if (lazyMan==null){ lazyMan = new LazyMan(); } } } return lazyMan; } public static void main(String[] args) { for (int i = 0; i < 10 ; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } } ``` > 靜態內部類 ```java // 靜態內部類 public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER = new Holder(); } } ``` 也是單例模式的一種,不安全! > 單例不安全 反射 ```java // 懶漢式單例 public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()+":: ok"); } private volatile static LazyMan lazyMan; // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式 public static LazyMan getInstance(){ if (lazyMan==null){ synchronized (LazyMan.class){ if (lazyMan==null){ lazyMan = new LazyMan(); //不是一個原子性操作 } } } return lazyMan; } //反射 public static void main(String[] args) throws Exception { LazyMan instance1 = LazyMan.getInstance(); Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); // 無視了私有的構造器 // 通過反射建立物件 LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } } ``` ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085115451-983189446.png) 結論:反射可以破壞這種單例 **解決** ```java // 懶漢式單例 public class LazyMan { private LazyMan(){ synchronized (LazyMan.class){ if (lazyMan!=null){ throw new RuntimeException("不要試圖使用反射破環 異常"); } } System.out.println(Thread.currentThread().getName()+":: ok"); } private volatile static LazyMan lazyMan; // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式 public static LazyMan getInstance(){ if (lazyMan==null){ synchronized (LazyMan.class){ if (lazyMan==null){ lazyMan = new LazyMan(); //不是一個原子性操作 } } } return lazyMan; } //反射 public static void main(String[] args) throws Exception { LazyMan instance1 = LazyMan.getInstance(); Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); // 無視了私有的構造器 // 通過反射建立物件 LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } } ``` ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085051712-1140711830.png) 但是如果都用反射建立物件的情況下,還是會破環單例! **測試** ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085022776-883662583.png) **解決** ```java // 懶漢式單例 public class LazyMan { // 標誌位 private static boolean abc = false; private LazyMan(){ synchronized (LazyMan.class){ if (abc==false){ abc=true; }else { throw new RuntimeException("不要試圖使用反射破環 異常"); } } System.out.println(Thread.currentThread().getName()+":: ok"); } private volatile static LazyMan lazyMan; // 雙重檢測鎖模式的 懶漢式單例 DCL懶漢式 public static LazyMan getInstance(){ if (lazyMan==null){ synchronized (LazyMan.class){ if (lazyMan==null){ lazyMan = new LazyMan(); //不是一個原子性操作 } } } return lazyMan; } //反射 public static void main(String[] args) throws Exception { //LazyMan instance1 = LazyMan.getInstance(); Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); // 無視了私有的構造器 // 通過反射建立物件 LazyMan instance2 = declaredConstructor.newInstance(); LazyMan instance1 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } } ``` ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530085004471-1299363875.png) 但是如果被人知道 `abc`這個變數,也可以破環! ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084949083-762775567.png) ==單例又被破環了!== 看一下原始碼 ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084934727-1890738756.png) 它說不能使用反射破環列舉,列舉是jdk1.5出現的,自帶單例模式! **測試**,寫一個列舉類 ```java // enum 本身就是一個class類 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } ``` 檢視它的原始碼 ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084906146-1966744325.png) 試圖破環! ```java // enum 本身就是一個class類 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws Exception { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } } ``` ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084842906-1366055976.png) 它竟然說我現在的這個列舉類中沒有空參構造器! 然後就去原始碼裡分析! ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084830441-2061828793.png) 找到這個class檔案!利用javap反編譯一下! ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084816237-568885889.png) 發現這個也顯示有一個空參構造,證明這個也不對,用第三方的工具檢視! ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084759223-356213598.png) 利用它再吧class檔案生成java檔案! ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084739520-1898020039.png) ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084730518-806779912.png) 開啟這個java檔案 ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084718059-24953770.png) **證明是idea和原始碼騙了我!** 再次嘗試破環! ```java // enum 本身就是一個class類 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws Exception { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } } ``` ![](https://img2020.cnblogs.com/blog/1869289/202005/1869289-20200530084700911-1549753380.png) **結論:反射無法破環列舉類!**