zoukankan      html  css  js  c++  java
  • 单例模式-2(注册式单例)

    引言

      序列化破坏单例:一个单例对象创建好后,有时候需要将对象序列化后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。反序列化后的对象将会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,看如下代码。

    public class SeriableSingleton implements Serializable {
    
        public final static SeriableSingleton INSTANCE = new SeriableSingleton();
    
        private SeriableSingleton(){}
        public static SeriableSingleton getInstance(){
            return INSTANCE;
        }
    }

    测试代码如下

        @Test
        void SeriableSingletonTest(){
            SeriableSingleton s1 = null;
            SeriableSingleton s2 = SeriableSingleton.getInstance();
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream("SeriableSingleton.obj");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(s2);
                oos.flush();
                oos.close();
    
                FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
                ObjectInputStream ois = new ObjectInputStream(fis);
                s1 = (SeriableSingleton)ois.readObject();
                ois.close();
                System.out.println(s1);
                System.out.println(s2);
                System.out.println(s1 == s2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    SeriableSingletonTest

    运行结果如下图

      从运行结果可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例模式的设计初衷。那么如何保证在序列化的情况下也能实现单例模式呢?只需要增加 readResolve() 方法即可。

    public class SeriableSingleton implements Serializable {
    
        public final static SeriableSingleton INSTANCE = new SeriableSingleton();
    
        private SeriableSingleton(){}
        public static SeriableSingleton getInstance(){
            return INSTANCE;
        }
        private Object readResolve(){
            return INSTANCE;
        }
    }

    运行结果如下图:

     

    读JDK 的 源码发现,虽然增加 readResolve() 方法返回实例解决了单例模式被破坏的问题,但是实际上实例化了两次,只不过新创建的对象没有被返回而已。如果创建对象的动作发生频率加快,就意味着内存分配的开销也会随之增大,这时候就注册式单例就可以登场了。

    注册式单例模式

      注册式单例模式又称为等级式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例i模式有两种:一种是枚举式单例模式,另一种是容器式单例模式。

    1.枚举式单例模式

      枚举式单例模式写法如下:

    public enum EnumSingleton {
        INSTANCE;
        private Object data;
        public Object getData() {
            return data;
        }
        public void setData(Object data) {
            this.data = data;
        }
        public static EnumSingleton getInstance(){
            return INSTANCE;
        }
    }

      测试代码如下:

        @Test
        void EnumSingletonTest(){
            EnumSingleton e1 = null;
            EnumSingleton e2 = EnumSingleton.getInstance();
            e2.setData(new Object());
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream("EnumSingleton.obj");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(e2);
                oos.flush();
                oos.close();
    
                FileInputStream fis = new FileInputStream("EnumSingleton.obj");
                ObjectInputStream ois = new ObjectInputStream(fis);
                e1 = (EnumSingleton)ois.readObject();
                ois.close();
                System.out.println(e1.getData());
                System.out.println(e2.getData());
                System.out.println(e1.getData() == e2.getData());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    EnumSingletonTest

      运行结果如下:

      枚举类型其实通过类名和类对象找到一个唯一的枚举对象。因此,枚举对象不可能别类加载器加载多次。

      那么反射能否破坏枚举式单例模式呢?测试代码如下:

        @Test
         void EnumSingletonTestThread() {
            try{
                Class clazz = EnumSingleton.class;
                Constructor c = clazz.getDeclaredConstructor();
                c.newInstance();
            } catch (Exception e){
                e.printStackTrace();
            }
        }

      运行结果如下:

     报错没有找到无参构造方法。查看 Enum 源码  他的构造方法只有一个 protected 类型的构造方法,代码如下:

      protected Enum(String name, int ordinal){ this.name = name; this.ordinal = ordinal; }  

      再做如下测试:

        @Test
         void EnumSingletonTestThread() {
            try{
                Class clazz = EnumSingleton.class;
                Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
                c.setAccessible(true);
                EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("l-coil",666);
            } catch (Exception e){
                e.printStackTrace();
            }
        }

       测试结果如下:

      

      报错已经很明显了,不能用反射来创建枚举类型。

      枚举式单例模式也是 Effective Java 书中推荐的一种单例模式实现写法。JDK 枚举的语法特殊性质及繁殖也为枚举报价护航,让枚举式单例模式成为一种比较优雅的实现。 

    2.容器式单例

      容器式单例写法如下:

    public class ContainerSingleton {
        private ContainerSingleton(){}
        private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
        public static Object getBean(String className){
            synchronized (ioc){
                if(!ioc.containsKey(className)){
                    Object obj = null;
                    try{
                        obj = Class.forName(className).newInstance();
                        ioc.put(className, obj);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    return obj;
                } else {
                  return ioc.get(className);
                }
            }
        }
    }

      容器式单例模式使用与实例非常多的情况,编辑管理。单它是非线程安全的。

     

    本文来自博客园,作者:l-coil,转载请注明原文链接:https://www.cnblogs.com/l-coil/p/12863405.html

  • 相关阅读:
    Java内存模型(JMM)
    线程安全问题的本质详解: 原子性、有序性、可见性
    Quartz实现分布式可动态配置的定时任务
    Java引用详解-StrongReference SoftReference WeakReference PhantomReference
    流行的报表生成工具-JXLS
    Java线程监控及中断
    IntelliJ IDEA 内存优化最佳实践
    Dapeng框架-开源高性能分布式微服务框架
    Scala实现Try with resources自动关闭IO
    Jvm启动,关闭及对应钩子
  • 原文地址:https://www.cnblogs.com/xianquan/p/12863405.html
Copyright © 2011-2022 走看看