乐趣区

单例模式拓展讲解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 判空

切换到线程模式 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@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. 如果存在则执行这个方法, 替换返回结果
退出移动版