介绍

主要介绍通过反射的方式获取单例对象, 验证单例模式的安全性.
主要从以下几个角度来介绍反射下的单例
饿汉式
双重锁检查
枚举单例

饿汉式

饿汉式直接使用反射即可破解单例模式

  1. public class ReflectTest {
  2. public static void main(String[] args) {
  3. try {
  4. HungerPattern hungerPattern = HungerPattern.getHungerPattern();
  5. Class<HungerPattern> hungerPatternClass = HungerPattern.class;
  6. Constructor<HungerPattern> conA = hungerPatternClass.getDeclaredConstructor();
  7. Constructor<HungerPattern> conB = hungerPatternClass.getDeclaredConstructor();
  8. conA.setAccessible(true);
  9. conB.setAccessible(true);
  10. HungerPattern instanceA = conA.newInstance();
  11. HungerPattern instanceB = conB.newInstance();
  12. // instanceA 和 instanceB 不是同一对象
  13. System.out.println(hungerPattern.hashCode());
  14. System.out.println(instanceA.hashCode());
  15. System.out.println(instanceB.hashCode());
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }

输出结果

  1. D:\jdk1.8\bin\java.exe . . .
  2. 713338599
  3. 168423058
  4. 821270929
  5. Process finished with exit code 0

双重锁检查

双重锁检查同样存在相同的情况

  1. 直接使用

    1. public class ReflectTest {
    2. public static void main(String[] args) {
    3. try {
    4. DoubleCheckLockLazyPattern pattern = DoubleCheckLockLazyPattern.getDoubleCheckLockLazyPattern();
    5. Class<DoubleCheckLockLazyPattern> patternClass = DoubleCheckLockLazyPattern.class;
    6. Constructor<DoubleCheckLockLazyPattern> conA = patternClass.getDeclaredConstructor();
    7. Constructor<DoubleCheckLockLazyPattern> conB = patternClass.getDeclaredConstructor();
    8. conA.setAccessible(true);
    9. conB.setAccessible(true);
    10. DoubleCheckLockLazyPattern patternA = conA.newInstance();
    11. DoubleCheckLockLazyPattern patternB = conA.newInstance();
    12. System.out.println(pattern.hashCode());
    13. System.out.println(patternA.hashCode());
    14. System.out.println(patternB.hashCode());
    15. } catch (Exception e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. }

    输出结果

    1. D:\jdk1.8\bin\java.exe . . .
    2. 713338599
    3. 168423058
    4. 821270929
    5. Process finished with exit code 0
  2. 在双重锁检查私有构造内加入异常

    1. public class DoubleCheckLockLazyPattern {
    2. private DoubleCheckLockLazyPattern() {
    3. // 加入异常判断, 防止反射
    4. if (doubleCheckLockLazyPattern != null) {
    5. throw new RuntimeException();
    6. }
    7. }
    8. private static volatile DoubleCheckLockLazyPattern doubleCheckLockLazyPattern = null;
    9. public static DoubleCheckLockLazyPattern getDoubleCheckLockLazyPattern() {
    10. try {
    11. if (doubleCheckLockLazyPattern == null) {
    12. // 一系列操作
    13. Thread.sleep(100);
    14. synchronized (DoubleCheckLockLazyPattern.class) {
    15. // 二次检查
    16. if (doubleCheckLockLazyPattern == null) {
    17. doubleCheckLockLazyPattern = new DoubleCheckLockLazyPattern();
    18. }
    19. }
    20. }
    21. } catch (InterruptedException e) {
    22. e.printStackTrace();
    23. }
    24. return doubleCheckLockLazyPattern;
    25. }
    26. }

    输出结果

    1. D:\jdk1.8\bin\java.exe . . .
    2. java.lang.reflect.InvocationTargetException
    3. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    4. at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    5. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    6. at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    7. at com.liuzhihang.demo.singleton.ReflectTest.main(ReflectTest.java:24)
    8. Caused by: java.lang.RuntimeException
    9. at com.liuzhihang.demo.singleton.DoubleCheckLockLazyPattern.<init>(DoubleCheckLockLazyPattern.java:15)
    10. ... 5 more
  3. 通过序列化反序列化获取对象

DoubleCheckLockLazyPattern 实现序列化

  1. public class ReflectTest {
  2. public static void main(String[] args) {
  3. try {
  4. DoubleCheckLockLazyPattern pattern = DoubleCheckLockLazyPattern.getDoubleCheckLockLazyPattern();
  5. FileOutputStream fos= new FileOutputStream("C:/Users/liuzhihang/desktop/pattern.txt");
  6. ObjectOutputStream oos = new ObjectOutputStream(fos);
  7. oos.writeObject(pattern);
  8. oos.close();
  9. fos.close();
  10. ObjectInputStream oisA = new ObjectInputStream(new FileInputStream("C:/Users/liuzhihang/desktop/pattern.txt"));
  11. DoubleCheckLockLazyPattern patternA= (DoubleCheckLockLazyPattern) oisA.readObject();
  12. ObjectInputStream oisB = new ObjectInputStream(new FileInputStream("C:/Users/liuzhihang/desktop/pattern.txt"));
  13. DoubleCheckLockLazyPattern patternB= (DoubleCheckLockLazyPattern) oisB.readObject();
  14. System.out.println(pattern.hashCode());
  15. System.out.println(patternA.hashCode());
  16. System.out.println(patternB.hashCode());
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }

输出结果

  1. D:\jdk1.8\bin\java.exe . . .
  2. 258952499
  3. 1702297201
  4. 1996181658
  5. Process finished with exit code 0
  1. 修改反序列化方法, 可以防止反序列化

添加以下方法

  1. private Object readResolve() {
  2. return doubleCheckLockLazyPattern;
  3. }

输出结果

  1. D:\jdk1.8\bin\java.exe . . .
  2. 258952499
  3. 258952499
  4. 258952499
  5. Process finished with exit code 0

枚举单例

  1. public enum SingletonEnum {
  2. /**
  3. * 单例
  4. */
  5. INSTANCE;
  6. private Resource resource;
  7. SingletonEnum() {
  8. this.resource = new Resource();
  9. }
  10. public Resource getResource() {
  11. return resource;
  12. }
  13. }
  14. class Resource {
  15. }

枚举单例分析

在枚举反射获取对象时抛出异常, 通过 Constructor类 源码可以看出, 在反射创建对象时会判断是否是枚举修饰, 是则抛出异常

  1. @CallerSensitive
  2. public T newInstance(Object ... initargs)
  3. throws InstantiationException, IllegalAccessException,
  4. IllegalArgumentException, InvocationTargetException
  5. {
  6. if (!override) {
  7. if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
  8. Class<?> caller = Reflection.getCallerClass();
  9. checkAccess(caller, clazz, null, modifiers);
  10. }
  11. }
  12. if ((clazz.getModifiers() & Modifier.ENUM) != 0)
  13. throw new IllegalArgumentException("Cannot reflectively create enum objects");
  14. ConstructorAccessor ca = constructorAccessor; // read volatile
  15. if (ca == null) {
  16. ca = acquireConstructorAccessor();
  17. }
  18. @SuppressWarnings("unchecked")
  19. T inst = (T) ca.newInstance(initargs);
  20. return inst;
  21. }

同时在父类 Enum类 中重写了 readObject方法, 所以枚举也可以避免反序列化

  1. /**
  2. * prevent default deserialization
  3. */
  4. private void readObject(ObjectInputStream in) throws IOException,
  5. ClassNotFoundException {
  6. throw new InvalidObjectException("can't deserialize enum");
  7. }