單例模式,反射破環?_包裝設計

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

餓漢式

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

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

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。