生活随笔
收集整理的這篇文章主要介紹了
【学习笔记】单例模式(枚举、校验锁、volatile、反射破坏)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
文章目錄
- 1. 餓漢式
- 2. 懶漢式
- 3. DCL 雙重校驗鎖懶漢式
- 4. 通過反射破壞DCL & 加鎖阻止
- 5. 通過不調用 getInstance() 來破壞單例
- 6. 通過反射來干擾信號量,從而破壞單例
- 7. 通過枚舉類實現單例,可以防止反射破壞單例
- 學 JUC 的時候順便摸了下單例模式,看的是狂神的JUC教程~
1. 餓漢式
public class HungrySingleton {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 static HungrySingleton INSTANCE
= new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return INSTANCE
;}public static void main(String[] args
) {System.out
.println(HungrySingleton.getInstance());}
}
2. 懶漢式
public class LazySingleton {private static LazySingleton INSTANCE
= null;private LazySingleton(){}public static LazySingleton getInstance(){if(INSTANCE
== null){INSTANCE
= new LazySingleton();}return INSTANCE
;}
}
3. DCL 雙重校驗鎖懶漢式
- Double Check Lock,判斷了兩次,加了一個 synchronized 鎖住代碼塊
- 為什么要判斷兩次:可能多個進程卡在 synchronized 鎖這步,所以進去后還要再判斷一次
- 不安全的原因:指令重排(見代碼注釋)
- 解決方法:加 volatile 關鍵字禁止指令重排(見第三行注釋代碼)
public class DCLSingleton {private static DCLSingleton INSTANCE
= null;private DCLSingleton(){}public static DCLSingleton getInstance(){if(INSTANCE
== null){synchronized (DCLSingleton.class){if(INSTANCE
== null){INSTANCE
= new DCLSingleton();}}}return INSTANCE
;}
}
- 從字節碼指令角度,分析指令重排影響:(圖源黑馬JVM視頻,侵刪)
- 可以看到,JIT 編譯器可能會優化,先 putstatic,把引用地址賦予 INSTANCE,然后再 Method 來初始化
- 但是,并發情況下,可能在賦予引用后,init 前,有其他線程訪問了 getInstance(),獲得了一個還未引用的 INSTANCE,導致出錯。
4. 通過反射破壞DCL & 加鎖阻止
- 可以通過反射 setAccessible 無視私有,使用構造器來破壞單例(見 main() 代碼)
- 阻止方法:在構造器再加一個 synchronized 鎖,并且進行反射破壞判斷并拋出異常
public class TripleSingleton {private static TripleSingleton INSTANCE
= null;private TripleSingleton(){synchronized (TripleSingleton.class){if(INSTANCE
!= null){throw new RuntimeException("不要通過反射來破壞單例模式");}}}public static TripleSingleton getInstance(){if(INSTANCE
== null){synchronized (TripleSingleton.class){if(INSTANCE
== null){INSTANCE
= new TripleSingleton();}}}return INSTANCE
;}public static void main(String[] args
) throws Exception{System.out
.println(TripleSingleton.getInstance());Constructor<TripleSingleton> constructor
= TripleSingleton.class.getDeclaredConstructor(null);constructor
.setAccessible(true);System.out
.println(constructor
.newInstance());}
}
5. 通過不調用 getInstance() 來破壞單例
- 既然 4 是基于 INSTANCE != null 的情況來判斷,那么我們只要不理 INSTANCE 就可以破壞單例模式了~
- 只通過反射得到的構造器來創造多個實例
- 解決方法:加一個信號量 flag,在構造函數中防止這種破壞方式
public class TripleSingleton2 {private static TripleSingleton2 INSTANCE
= null;private static boolean flag
= false;private TripleSingleton2(){synchronized (TripleSingleton2.class){if(flag
== false){flag
= true;}else {throw new RuntimeException("不要通過反射來破壞單例模式");}}}public static TripleSingleton2 getInstance(){if(INSTANCE
== null){synchronized (TripleSingleton2.class){if(INSTANCE
== null){INSTANCE
= new TripleSingleton2();}}}return INSTANCE
;}public static void main(String[] args
) throws Exception{Constructor<TripleSingleton2> constructor
= TripleSingleton2.class.getDeclaredConstructor(null);constructor
.setAccessible(true);System.out
.println(constructor
.newInstance());System.out
.println(constructor
.newInstance());}
}
6. 通過反射來干擾信號量,從而破壞單例
- 只要通過反射來干擾信號量,就可以繼續破壞單例模式了~(見 main() 代碼)
package singletons;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class TripleSingleton3 {private static TripleSingleton3 INSTANCE
= null;private static boolean flag
= false;private TripleSingleton3(){synchronized (TripleSingleton3.class){if(flag
== false){flag
= true;}else {throw new RuntimeException("不要通過反射來破壞單例模式");}}}public static TripleSingleton3 getInstance(){if(INSTANCE
== null){synchronized (TripleSingleton3.class){if(INSTANCE
== null){INSTANCE
= new TripleSingleton3();}}}return INSTANCE
;}public static void main(String[] args
) throws Exception{Field flag
= TripleSingleton3.class.getDeclaredField("flag");flag
.setAccessible(true);Constructor<TripleSingleton3> constructor
= TripleSingleton3.class.getDeclaredConstructor(null);constructor
.setAccessible(true);TripleSingleton3 instance1
= constructor
.newInstance();flag
.set(instance1
, false);TripleSingleton3 instance2
= constructor
.newInstance();System.out
.println(instance1
+ "\n" + instance2
);}
}
7. 通過枚舉類實現單例,可以防止反射破壞單例
- 為了進行反射破壞,先要獲取 enum 類的構造器
- 觀測源碼,發現有無參構造函數,然而實際上這個是假的= =。
- 使用 javap 反編譯,會發現還是有無參構造函數
- 使用 jad,會找到一個 private EnumSingle(String s, int i) 的構造函數,成功~
public enum EnumSingleton {INSTANCE
;public static void main(String[] args
) throws Exception{System.out
.println(EnumSingleton.INSTANCE
);Constructor<EnumSingleton> constructor
= EnumSingleton.class.getDeclaredConstructor(String.class, int.class);System.out
.println(constructor
.newInstance());}
}
- 會爆出這個錯誤:
- 更加深入可以去看看 Enum 的源碼~
總結
以上是生活随笔為你收集整理的【学习笔记】单例模式(枚举、校验锁、volatile、反射破坏)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。