單例模式,反射破環?

一條有夢想的鹹魚。發表於2020-05-30

餓漢式

// 餓漢式單例
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,多執行緒併發就會出現問題!

測試

// 懶漢式單例
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();
         }
     }
}

但在極端情況下還是可能出現問題

經歷三個步驟:

1、 分配記憶體空間

2、 執行構造方法,初始化物件

3、 把這個物件指向這個空間

有可能會發生指令重排的操作!

比如,期望它執行 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);
    }
}

結論:反射無法破環列舉類!

相關文章