单例设计模式

来源:互联网 发布:手机淘宝扫一扫 编辑:程序博客网 时间:2024/04/30 06:05

单例设计模式的常见场景

单例设计模式的五种实现

  • 懒汉模式
package com.pattern.gof;/** * 测试懒汉模式 *  * 类加载器在加载的时候,就new 一个实例,非延时加载 *  * @author Nicholas * */public class SingletonDemo2 {    /**     * 1、构造器私有 2、提供静态属性,不初始化 3、提供静态的访问接口,要同步 这是个懒加载,用的时候,才去创建 效率不高,多个线程,必须同步     *      */    private static SingletonDemo2 instance = null;    // 1、构造器私有    private SingletonDemo2() {    }    // 3、提供静态的访问接口,需要synchronized同步,防止创建多个对象    public static synchronized SingletonDemo2 GetInstance() {        if (instance == null)            instance = new SingletonDemo2();        return instance;    }}
  • 饿汉模式
package com.pattern.gof;import java.io.Serializable;/** * 测试饿汉式单例模式 *  * 类加载器在加载的时候,就new 一个实例,非延时加载 *  * @author Nicholas * */public class SingletonDemo1 implements Serializable {    /**     * @Fields serialVersionUID     */    private static final long serialVersionUID = 1L;    /**     * 1、构造器私有 2、提供静态属性 同时new 初始化 3、提供静态的访问接口,不需要同步     *      * 类加载器加载一个类的时候,是天然的线程安全模式。所以饿汉式是线程安全的,效率也很高。     *      * 资源利用效率不高。     */    // 2、提供静态属性    private static SingletonDemo1 instance = new SingletonDemo1();    // 1、构造器私有    private SingletonDemo1() {    }    // 3、提供静态的访问接口,不需要synchronized同步    public static SingletonDemo1 GetInstance() {        return instance;    }}
  • 双重检测锁
package com.pattern.gof;/** * 双重检测锁实现 延时加载 * 由于编译器优化,可能会出现问题 * @author Nicholas * *  同步代码快而不是同步方法,效率有所提高 *  只在第一次加载的时候同步,后面不同步 */public class SingletonDemo3 {    private static SingletonDemo3 instance = null;    private SingletonDemo3() {    }    public static SingletonDemo3 getInstance() {        if (instance == null) {            SingletonDemo3 singletonDemo_3;            synchronized (SingletonDemo3.class) {                singletonDemo_3 = instance;                if (singletonDemo_3 == null) {                    synchronized (SingletonDemo3.class) {                        if (singletonDemo_3 == null) {                            singletonDemo_3 = new SingletonDemo3();                        }                    }                    instance = singletonDemo_3;                }            }        }        return instance;    }}
  • 静态内部类
package com.pattern.gof;/** * 静态内部类实现 ,也是懒加载 *  * @author Nicholas instance是static final类型,确保了只有一个,所以是绝对的线程安全的。 同时兼备懒加载和高并发的优势。 * 只有调用getInstance()时,才会去加载 */public class SingletonDemo4 {    private static class SingletonDemo_4Instance {        private static final SingletonDemo4 instance = new SingletonDemo4();    }    // 构造器私有    private SingletonDemo4() {    }    // 提供对外的接口    public static SingletonDemo4 getInstance() {        return SingletonDemo_4Instance.instance;    }}
  • 枚举
package com.pattern.gof;/** * 枚举本身就是单例,这个是立即加载,实现也比较简单 *  * @author Nicholas * */public enum SingletonDemo5 {    // 定义一个枚举变量,代表SingletonDemo_5的一个实例    INSTANCE;    public static void main(String[] args) {        SingletonDemo5 singletonDemo1 = SingletonDemo5.INSTANCE;        SingletonDemo5 singletonDemo2 = SingletonDemo5.INSTANCE;        System.out.println(singletonDemo1 == singletonDemo2);    }}

枚举模式的漏洞(除了枚举)

  1. 反射破解单例
package com.pattern.gof;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;/** * @ClassName: TestOfSingleton * @Description: 测试单例模式的懒汉模式,在序列化和反序列化时会出现异常 * @author 韦轩 * @date 2015年7月12日 下午5:57:53 * */public class TestOfSingleton {    public static void main(String[] args) {        SingletonDemo1 singletonDemo1 = SingletonDemo1.GetInstance();        SingletonDemo1 singletonDemo2 = SingletonDemo1.GetInstance();        System.out.println(singletonDemo1);        System.out.println(singletonDemo2);        System.out.println(singletonDemo1 == singletonDemo2);        // 通过反射的方式破解单例模式(除枚举的方式之外)        try {            @SuppressWarnings("unchecked")            Class<SingletonDemo1> class1 = (Class<SingletonDemo1>) Class                    .forName("com.pattern.gof.SingletonDemo1");            // 获取无参构造器            Constructor<SingletonDemo1> constructor = class1                    .getDeclaredConstructor(null);            /**             * java.lang.IllegalAccessException:             */            constructor.setAccessible(true);            SingletonDemo1 s1 = constructor.newInstance();            SingletonDemo1 s2 = constructor.newInstance();            /**             * s1和s2会出现不一致的状况 如何解决这个问题? 在私有的构造器中判断,如果instance不为空,抛出异常             */            System.out.println(s1);            System.out.println(s2);            System.out.println(s1 == s2);        } catch (ClassNotFoundException | NoSuchMethodException                | SecurityException | InstantiationException                | IllegalAccessException | IllegalArgumentException                | InvocationTargetException e) {            e.printStackTrace();        }    }}

输出结果,通过普通方式创建的对象是同一个,但是通过反射创建的对象,会出现不一致。解决办法是在私有的构造器中判断instance是否为空,抛出异常

com.pattern.gof.SingletonDemo1@659e0bfdcom.pattern.gof.SingletonDemo1@659e0bfdtruecom.pattern.gof.SingletonDemo1@2a139a55com.pattern.gof.SingletonDemo1@15db9742false

解决办法

    private CopyOfSingletonDemo1() {        /**         * 用抛出异常的形式来屏蔽反序列化中创建多个实例         */        if (instance != null)            throw new RuntimeException("Can't create  another object ...");    }
  1. 反序列化破解枚举
package com.pattern.gof;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;/** * @ClassName: TestOfSingleton * @Description: 测试单例模式的懒汉模式,在序列化和反序列化时会出现异常 * @author 韦轩 * @date 2015年7月12日 下午5:57:53 * */public class TestOfSingleton {    public static void main(String[] args) {        SingletonDemo1 singletonDemo1 = SingletonDemo1.GetInstance();        SingletonDemo1 singletonDemo2 = SingletonDemo1.GetInstance();        System.out.println(singletonDemo1);        System.out.println(singletonDemo2);        System.out.println(singletonDemo1 == singletonDemo2);        // 通过序列化将对象写入到硬盘        try {            FileOutputStream fileOutputStream = new FileOutputStream(                    "D:\\J2EE\\GOF_pattern\\object.txt");            ObjectOutputStream objectOutputStream = new ObjectOutputStream(                    fileOutputStream);            System.out.println(singletonDemo1);            objectOutputStream.writeObject(singletonDemo1);        } catch (IOException e) {            e.printStackTrace();        }        // 进行反序列化        try {            ObjectInputStream objectInputStream = new ObjectInputStream(                    new FileInputStream("D:\\J2EE\\GOF_pattern\\object.txt"));            SingletonDemo1 sd1 = (SingletonDemo1) objectInputStream.readObject();            System.out.println(sd1);        } catch (IOException | ClassNotFoundException e) {            e.printStackTrace();        }    }}

这里,我们是将singletonDemo1序列化到硬盘,然后发序列化,
结果是不一致的状况

com.pattern.gof.SingletonDemo1@659e0bfdcom.pattern.gof.SingletonDemo1@3d4eac69

这种情况的解决办法是在单例模式的实现类中试下一个方法,当反序列化时,直接返回已经有的对象

    /**     *      * @Title: readResolve     * @Description: 在反序列化时,直接返回已经创建的实力对象     * @param @return     * @param @throws ObjectStreamException     * @return Object     * @throws     */    private Object readResolve() throws ObjectStreamException {        return instance;    }

修改后的枚举实现

这里只实现了饿汉式

package com.pattern.gof;import java.io.ObjectStreamException;import java.io.Serializable;/** * * @ClassName: NewSingleton * @Description: 单例模式的饿汉式实现* @author 韦轩* @date 2015年7月12日 下午7:27:13 * */public class NewSingleton implements Serializable {    /**     * @Fields serialVersionUID     */    private static final long serialVersionUID = 1L;    private static NewSingleton instance = new NewSingleton();    private NewSingleton() {        //屏蔽反射创建多个对象,出现不一致的情况        if (instance != null)            throw new RuntimeException("Can't create another object ...");    }    public static NewSingleton GetInstance() {        return instance;    }    /**     *      * @Title: readResolve     * @Description: 在反序列化时,直接返回已经创建的实例对象     * @param @return     * @param @throws ObjectStreamException     * @return Object     */    private Object readResolve() throws ObjectStreamException {        return instance;    }}

五种实现的对比


效率测试

package com.pattern.gof;import java.util.concurrent.CountDownLatch;/** *  * @ClassName: TestEfficiency * @Description: 测试五种单例模式的效率 * @author 韦轩 * @date 2015年7月12日 下午6:39:37 * */public class TestEfficiency {    public static void main(String[] args) {        long startTime = System.currentTimeMillis();        int threadNumber = 10;        CountDownLatch countDownLatch = new CountDownLatch(threadNumber);        /**         * 开启十个线程,每个线程调用SingletonDemo1.GetInstance();10W次         */        for (int i = 0; i < 10; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    for (int i = 0; i < 100000; i++) {                        Object object = SingletonDemo1.GetInstance(); //16                        //Object object = SingletonDemo2.GetInstance();//49                        //Object object = SingletonDemo3.getInstance(); //11                        //Object object = SingletonDemo4.getInstance(); //19                        //Object object = SingletonDemo5.INSTANCE; //11                    }                    countDownLatch.countDown();                }            }).start();        }        //等待执行完成        try {            countDownLatch.await();         } catch (InterruptedException e) {            e.printStackTrace();        }        long endTime = System.currentTimeMillis();        System.out.println("Total time :" + (endTime - startTime));    }}
0 0
原创粉丝点击