单例模式拓展

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();    }}

类加载的顺序

  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@6f94fa3ecom.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@6e8cf4c6com.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. 如果存在则执行这个方法, 替换返回结果