單例模式拓展
Author : HuiFer
Git-Repo: JavaBook-src
JAVA 中的單例
- Runtime
- ServletContext
- ServletConfig
- ApplicationContext
- 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
第一個線程進入
第二個線程不允許進入
線程狀態 MONITOR 監聽狀態
第一個線程執行完成後纔會變成 RUNNING
雙重校驗
public synchronized static SimpleSingleton getInstance01() {
if (lazy == null) {
synchronized (SimpleSingleton.class) {
if (lazy == null) {
lazy = new SimpleSingleton();
}
}
}
return lazy;
}
- 同樣進行debug
通過兩張圖我們可以發現 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();
}
}
類加載的順序
- 加載 LazyInnerClassSingleton 之前加載 LazyObj
- 在調用 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;
}
- 爲什麼寫了這個方法有用呢?
-
首先我們讀取obj的方法是
ois.readObject()
java.io.ObjectInputStream#readObject0
- 在 readObject0中有如下代碼繼續追蹤
java.io.ObjectInputStream#readOrdinaryObject
這裏desc.newInstance()
創建了一個因此不相同
同一個方法繼續往下走
- 這裏在判斷是否存在
readResolve
方法- 如果存在則執行這個方法, 替換返回結果