工作生活

实用设计模式-Java单例模式

2019-07-04  本文已影响0人  DoubleFooker

常见单例模式

1.饿汉式

/**
* 缺点:有现场创建资源,如果没用到造成内存浪费
*/
public class HungrySingleton implements Serializable {
    /**
     * 私有化构造方法
     */
    private HungrySingleton() {
    }
    /**
     * 或者 静态代码块初始化
     * private static final HungrySingleton HUNGRY_SINGLETON;
     * static{
     * HUNGRY_SINGLETON = new HungrySingleton();
     * }
     */
    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
}

测试用例

public class HungrySingletonTest {

    /**
     * 单例校验
     */
    @Test
    public void testSingleton() {
        Set obj = new ConcurrentSkipListSet();
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                HungrySingleton instance = HungrySingleton.getInstance();
                if (obj.add(instance.toString())) {
                    System.out.println(instance.toString());
                }
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }).start();
            countDownLatch.countDown();
        }
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

2.懒汉式

public class LazySingleton implements Serializable {
    private LazySingleton() {
    }

    private static LazySingleton LAZY_SINGLETON;

    /**
     * synchronized 关键字添加锁,性能下降
     * @return
     */
    public static synchronized LazySingleton getInstance() {
        if (LAZY_SINGLETON == null) {
            LAZY_SINGLETON = new LazySingleton();
        }
        return LAZY_SINGLETON;

    }
}

3. Double Check 懒汉式

public class DoubleCheckSingleton {
    private DoubleCheckSingleton() {
    }

    /**
     * volatile 关键字防止 JVM指令重排序
     */
    private volatile static DoubleCheckSingleton DOUBLECHECK_SINGLETON;

    public static DoubleCheckSingleton getInstance() {
        /**
         *  synchronized不加在方法上 确保只在初始化时同步
         *  但是如果不用volatile修饰,存在指令重排序问题
         *   DOUBLECHECK_SINGLETON = new DoubleCheckSingleton();可分解为指令
         * memory = allocate();   //1:分配对象的内存空间
         * ctorInstance(memory);  //2:初始化对象
         * instance = memory;     //3:设置instance指向刚分配的内存地址
         * JVM指令重排序后可能出现
         * memory = allocate();   //1:分配对象的内存空间
         * instance = memory;     //3:设置instance指向刚分配的内存地址,此时对象还没有被初始化!
         * ctorInstance(memory);  //2:初始化对象
         *
         */
        if (DOUBLECHECK_SINGLETON == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (DOUBLECHECK_SINGLETON == null) {
                    DOUBLECHECK_SINGLETON = new DoubleCheckSingleton();
                }
            }
        }
        return DOUBLECHECK_SINGLETON;
    }
}

4.静态内部类

public class InnerClassSingleton {
    private InnerClassSingleton() {
    }

    /**
     * 调用InnercClassHolder才创建实例
     *
     * @return
     */
    public static InnerClassSingleton getInstance() {
        return InnerClassHolder.LAZY;
    }

    private static class InnerClassHolder {
        private static final InnerClassSingleton LAZY = new InnerClassSingleton();
    }
}

5.枚举实现注册式

Effective Java 推荐实现方式,枚举天生不支持反射创建,也不存在序列化破坏单例问题

public enum RegSingleton {
    INSTANCE;

    public int doSomeing() {
        return INSTANCE.hashCode();
    }
}

测试用例

   @Test
    public void testSingleton() {
        Set obj = new ConcurrentSkipListSet();
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                if (obj.add(RegSingleton.INSTANCE.doSomeing())) {
                    System.out.println(RegSingleton.INSTANCE.doSomeing());
                }
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }).start();
            countDownLatch.countDown();
        }
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

反射对单例的破坏

 /**
     * 反射破坏单例
     * 解决方案 在私有构造方法抛出异常
     */
    @Test
    public void refSingleton() {
        try {
            Class<?> clazz = HungrySingleton.class;
            Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            HungrySingleton instance1 = (HungrySingleton)       
                declaredConstructor.newInstance();
            System.out.println(instance1);
            HungrySingleton instance2 = HungrySingleton.getInstance();
            System.out.println(instance2);
            System.out.println(instance1 == instance2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

解决方案

  /**
   * 解决反射破坏单例
   */
 private HungrySingleton() {
     if (HUNGRY_SINGLETON != null) {
         throw new RuntimeException("不允许反射创建实例");
     }
 }

序列化对单例的破坏

    /**
     * 序列化破坏单例
     */
    @Test
    public void seriable() {
        try {
            HungrySingleton instance = HungrySingleton.getInstance();
            FileOutputStream out = new FileOutputStream("HungrySingleton");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
            objectOutputStream.writeObject(instance);
            out.close();
            objectOutputStream.close();
            FileInputStream in = new FileInputStream(new File("HungrySingleton"));
            ObjectInputStream objectInputStream = new ObjectInputStream(in);
            HungrySingleton hungrySingleton = (HungrySingleton) 
                objectInputStream.readObject();
            in.close();
            objectInputStream.close();
            System.out.println(instance);
            System.out.println(hungrySingleton);
            System.out.println(instance == hungrySingleton);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

解决方案,JDK在反序列化时会检测readResolve方法,通过复写方法防止新建实例。

    /**
     * 防止序列化破坏单例
     * JDK反序列化时 使用反射调用无参构造函数创建实例
     * @return
     */
    public Object readResolve() {
        return HUNGRY_SINGLETON;
    }

上一篇下一篇

猜你喜欢

热点阅读