单例模式拓展
Author : HuiFer
Git-Repo: JavaBook-src
懒汉式的多线程调试过程
- 写一个懒汉式
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
方法- 如果存在则执行这个方法, 替换返回结果