转载

单例模式拓展讲解-JAVA

单例模式拓展

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 判空

单例模式拓展讲解-JAVA

切换到线程模式debug

单例模式拓展讲解-JAVA

下方会出现一个debug 窗口

单例模式拓展讲解-JAVA

我们将两个线程都执行到 断点

根据代码我们可以知道只要有先后顺序就会得到一个单例对象.一旦同时进入就可能得到两个对象 。 通过debug 进行论证

让第一个线程进入 if 语句

单例模式拓展讲解-JAVA

让第二个线程进入

单例模式拓展讲解-JAVA

此时观察输出结果

结束
当前线程 Thread-1,当前对象com.huifer.design.singleton.nw.SimpleSingleton@52fd9092
当前线程 Thread-0,当前对象com.huifer.design.singleton.nw.SimpleSingleton@5a28dc04

因此上面的写法是线程不安全的

synchronized

再次debug

第一个线程进入

单例模式拓展讲解-JAVA

第二个线程不允许进入

单例模式拓展讲解-JAVA

线程状态 MONITOR 监听状态

第一个线程执行完成后才会变成 RUNNING

双重校验

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

        return lazy;
    }
  • 同样进行debug

单例模式拓展讲解-JAVA

单例模式拓展讲解-JAVA

通过两张图我们可以发现 Thread-0 状态未 MONITOR 在等待 Thread-1 执行完成 , 切换到Thtread-1 走完

单例模式拓展讲解-JAVA

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

单例模式拓展讲解-JAVA

  • 在 readObject0中有如下代码继续追踪

单例模式拓展讲解-JAVA

  • java.io.ObjectInputStream#readOrdinaryObject

单例模式拓展讲解-JAVA

这里 desc.newInstance() 创建了一个因此不相同

同一个方法继续往下走

单例模式拓展讲解-JAVA

  • 这里在判断是否存在 readResolve 方法

    1. 如果存在则执行这个方法, 替换返回结果
原文  https://segmentfault.com/a/1190000023030851
正文到此结束
Loading...