單例模式拓展講解-JAVA

單例模式拓展

Author : HuiFer
Git-Repo: JavaBook-src

JAVA 中的單例

  1. Runtime
  2. ServletContext
  3. ServletConfig
  4. ApplicationContext
  5. DBPoll

懶漢式的多線程調試過程

  • 寫一個懶漢式
public class SimpleSingleton {
    public static SimpleSingleton lazy = null;

    private SimpleSingleton() {

    }

    public static SimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new SimpleSingleton();
        }

        return lazy;
    }
}
  • 創建一個線程對象
public class ExecutorThread implements Runnable {

    @Override
    public void run() {
        SimpleSingleton instance = SimpleSingleton.getInstance();
        System.out.println("當前線程 " + Thread.currentThread().getName() + ",當前對象" + instance);
    }
}
  • 測試類
public class SimpleSingletonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new ExecutorThread());
        Thread t2 = new Thread(new ExecutorThread());
        t1.start();
        t2.start();
    }
}
  • 斷點給到 lazy 判空

在這裏插入圖片描述

切換到線程模式debug

在這裏插入圖片描述

下方會出現一個debug 窗口

在這裏插入圖片描述

我們將兩個線程都執行到 斷點

根據代碼我們可以知道只要有先後順序就會得到一個單例對象.一旦同時進入就可能得到兩個對象 。 通過debug 進行論證

讓第一個線程進入 if語句

在這裏插入圖片描述

讓第二個線程進入

在這裏插入圖片描述

此時觀察輸出結果

結束
當前線程 Thread-1,當前對象com.huifer.design.singleton.nw.SimpleSingleton@52fd9092
當前線程 Thread-0,當前對象com.huifer.design.singleton.nw.SimpleSingleton@5a28dc04

因此上面的寫法是線程不安全的

synchronized

再次debug

第一個線程進入

在這裏插入圖片描述

第二個線程不允許進入

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AEhtrtEn-1593263842011)(assets/image-20200627202953735.png)]

線程狀態 MONITOR 監聽狀態

第一個線程執行完成後纔會變成 RUNNING

雙重校驗

public synchronized static SimpleSingleton getInstance01() {
        if (lazy == null) {
            synchronized (SimpleSingleton.class) {
                if (lazy == null) {
                    lazy = new SimpleSingleton();
                }
            }
        }

        return lazy;
    }
  • 同樣進行debug

在這裏插入圖片描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UPoZGHhq-1593263842013)(assets/image-20200627203957988.png)]

通過兩張圖我們可以發現 Thread-0 狀態未 MONITOR 在等待 Thread-1 執行完成 , 切換到Thtread-1 走完

在這裏插入圖片描述

Thread-0 也走完

此時輸出結果

當前線程 Thread-1,當前對象com.huifer.design.singleton.nw.SimpleSingleton@63668bdb
當前線程 Thread-0,當前對象com.huifer.design.singleton.nw.SimpleSingleton@63668bdb

內部類

public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton() {
    }

    public static LazyInnerClassSingleton getInstance() {
        return LazyObj.lazy;
    }

    private static class LazyObj {
        public static final LazyInnerClassSingleton lazy = new LazyInnerClassSingleton();

    }
}

類加載的順序

  1. 加載 LazyInnerClassSingleton 之前加載 LazyObj
  2. 在調用 getInstance LazyObj 的靜態變量已經初始化完成

攻擊

反射攻擊

public class LazyInnerClassSingletonTest {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<LazyInnerClassSingleton> clazz = LazyInnerClassSingleton.class;
        Constructor<LazyInnerClassSingleton> declaredConstructor = clazz.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyInnerClassSingleton lazyInnerClassSingleton = declaredConstructor.newInstance(null);
        // 輸出地址
        System.out.println(lazyInnerClassSingleton);
        // 輸出地址
        System.out.println(LazyInnerClassSingleton.getInstance());
    }

}
com.huifer.design.singleton.nw.LazyInnerClassSingleton@6f94fa3e
com.huifer.design.singleton.nw.LazyInnerClassSingleton@5e481248
  • 最簡單的方案 , 不允許構造即可

    	private LazyInnerClassSingleton() {
            throw new RuntimeException("ex");
        }
    

序列化攻擊

  • 餓漢式單例
public class SerializableSingleton implements Serializable {
    private static final SerializableSingleton singleton = new SerializableSingleton();

    private SerializableSingleton() {
    }


    public static SerializableSingleton getInstance() {
        return singleton;
    }
}
  • 攻擊代碼
public class SerializableSingletonTest {

    public static void main(String[] args) {
        SerializableSingleton s1 = null;
        SerializableSingleton s2 = SerializableSingleton.getInstance();
        FileOutputStream fos = null;
        try {
            // 寫出去
            fos = new FileOutputStream("SerializableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            // 讀進來
            FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);

            s1 = (SerializableSingleton) ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);

        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 執行結果
com.huifer.design.singleton.nw.SerializableSingleton@6e8cf4c6
com.huifer.design.singleton.nw.SerializableSingleton@355da254
  • readResolve 方法
    private Object readResolve() {
        return singleton;
    }
  • 爲什麼寫了這個方法有用呢?
  1. 首先我們讀取obj的方法是 ois.readObject()

    java.io.ObjectInputStream#readObject0

在這裏插入圖片描述

  • 在 readObject0中有如下代碼繼續追蹤

在這裏插入圖片描述

  • java.io.ObjectInputStream#readOrdinaryObject

在這裏插入圖片描述

這裏desc.newInstance() 創建了一個因此不相同

同一個方法繼續往下走

在這裏插入圖片描述

  • 這裏在判斷是否存在 readResolve 方法
    1. 如果存在則執行這個方法, 替換返回結果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章