餓漢式java
// 餓漢式單例 public class Hungry { //構造器私有 private Hungry(){ } // 一上來就把這個類加載了 private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
// 餓漢式單例 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; } }
餓漢式單例可能會形成浪費空間,因此想要用的時候再去建立這個對象,平時就先放在這個地方,因而就出現了懶漢式!安全
懶漢式多線程
// 懶漢式單例 public class LazyMan { // 構造器私有 private LazyMan(){ } private static LazyMan lazyMan; public static LazyMan getInstance(){ if (lazyMan==null){ lazyMan = new LazyMan(); } return lazyMan; } }
它是有問題的,單線程下確實單例ok,多線程併發就會出現問題!併發
測試ide
// 懶漢式單例 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(); } } }
發現單例有問題,每次結果可能都不同!工具
解決測試
// 懶漢式單例 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(); } } }
但在極端狀況下仍是可能出現問題idea
經歷三個步驟:線程
一、 分配內存空間code
二、 執行構造方法,初始化對象
三、 把這個對象指向這個空間
有可能會發生指令重排的操做!
好比,指望它執行 123 ,可是它真實可能執行132,好比第一個A線程過來執行了132,先分配空間再吧這個空間佔用了,佔用以後再去執行構造方法,若是如今忽然來了個B線程,因爲A已經指向這個空間了,它會覺得這個 lazyMan 不等於 null ,直接return ,此時lazyMan尚未完成構造,因此必須避免這個問題!
必須加上volatile
// 懶漢式單例 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(); } } }
靜態內部類
// 靜態內部類 public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER = new Holder(); } }
也是單例模式的一種,不安全!
單例不安全 反射
// 懶漢式單例 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<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); // 無視了私有的構造器 // 經過反射建立對象 LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
結論:反射能夠破壞這種單例
解決
// 懶漢式單例 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<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); // 無視了私有的構造器 // 經過反射建立對象 LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
可是若是都用反射建立對象的狀況下,仍是會破環單例!
測試
解決
// 懶漢式單例 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<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); // 無視了私有的構造器 // 經過反射建立對象 LazyMan instance2 = declaredConstructor.newInstance(); LazyMan instance1 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
可是若是被人知道 abc
這個變量,也能夠破環!
單例又被破環了!
看一下源碼
它說不能使用反射破環枚舉,枚舉是jdk1.5出現的,自帶單例模式!
測試,寫一個枚舉類
// enum 自己就是一個class類 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } }
查看它的源碼
試圖破環!
// 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<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
它居然說我如今的這個枚舉類中沒有空參構造器!
而後就去源碼裏分析!
找到這個class文件!利用javap反編譯一下!
發現這個也顯示有一個空參構造,證實這個也不對,用第三方的工具查看!
利用它再吧class文件生成java文件!
打開這個java文件
證實是idea和源碼騙了我!
再次嘗試破環!
// 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<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
結論:反射沒法破環枚舉類!