zoukankan      html  css  js  c++  java
  • 单例模式

    https://www.jianshu.com/u/4b8ac8f11b7d

    定义:确保某一个类只有一个实例,自行实例化并且向整个系统提供这个实例。

    单例模式的优点:

    1. 提高效率
    2. 避免对资源的多重占用
    3. 在系统设置全局访问点,优化和共享资源访问。

    单例模式的缺点:

    1. 单例模式一般没有接口,扩展困难。
    2. 单例测试对于测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的。没有接口,也不能使用mock的方式虚拟一个对象。
    3. 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心是否是单例的。单例模式把单例和业务逻辑融合在一个类中。

    单例模式的应用场景

    在一个系统中,要求一个类有且仅有一个实例,如果出现多个实例就会出现副作用,可以采用单例模式。具体如下:

    1. 要求生成唯一序列号的环境。
    2. 在整个项目中需要共享一个访问点或者数据。
    3. 创建和销毁一个对象需要消耗的资源过多,但是又经常用到。如,要访问IO和数据库连接等。
    4. 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(也可以直接声明为static)。
    5. 需要频繁的进行创建和销毁的对象。

    单例模式的最佳实践

    单例模式比较简单,也应用广泛。在Spring中,每个Bean默认是单例的,优点是Spring容器可以管理这些Bean的生命周期,决定对象的创建和销毁时机,以及创建和销毁对象时的处理。

    单例模式常见的实现方式

    单例模式的实现可以分为两类:饿汉式(饥汉式)和懒汉式。
    饿汉式:在程序启动或单例模式类被加载的时候,单例模式实例就已经被创建。
    懒汉式:当程序第一次访问单例模式实例的时候才进行创建。
    以上两种方式各有优点。

    • 如果单例模式实例在系统中会被频繁用到,饿汉式比较好
      优点:程序启动的时候已经进行了实例化,调用时直接返回实例,速度快,效率高。
      缺点:如果实例使用频率不高或者几乎不用,启动的时候就进行实例化,浪费内存资源。
    • 如果单例模式实例在系统中很少用到或者几乎不会用到,懒汉式较好
      优点:如果实例使用频率不高或者几乎不用,启动的时候就不进行实例化,第一次调用的时候进行实例化(lazy-loading),节约内存资源。
      缺点:单例模式的实例如果被频繁调用,影响效率。

    代码实现(较为实用)


    双重检查:

      
    public class Singleton {
        private static volatile Singleton singleton;
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

    设置volatile是为了保证可见性,避免重排序。

     singleton = new Singleton();包含三个动作:1:分配内存给这个对象;2:初始化对象;3:设置了引用指向刚分配内存的对象。2、3有可能重排序
    lazy-load的单例可能被破坏:
    序列化和反序列化会破坏单例:可通过重写readResolve方法返回当前对象加以保护
    public class SerializableDemo1 {
        //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
        //Exception直接抛出
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            //Write Obj to file
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(Singleton.getSingleton());
            //Read Obj from file
            File file = new File("tempFile");
            ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
            Singleton newInstance = (Singleton) ois.readObject();
            //判断是否是同一个对象
            System.out.println(newInstance == Singleton.getSingleton());
        }
    }

    通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。

     

    防止序列化破坏单例模式

    先给出解决方案,然后再具体分析原理:

    只要在Singleton类中定义readResolve就可以解决该问题:

    public class Singleton implements Serializable{
        private volatile static Singleton singleton;
        private Singleton (){}
        public static Singleton getSingleton() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    
        private Object readResolve() {
            return singleton;
        }
    }

    反射攻击

    什么是反射攻击呢?

    1. 在Java中,由于反射的功能实在是太强了,通过动态访问类并设置Access使得可以访问对象的私有属性方法等。
    2. 在单例模式中,我们使用private 修饰构造方法对外隐藏,防止外部new 对象,但是在反射的存在下,private的存在形同虚设,通过反射设置Access即可访问构造方法,这时的单例就不是单例了。
      try {
                Constructor<CommonUtil> constructor = CommonUtil.class.getDeclaredConstructor(Class.class);
                constructor.setAccessible(true);//将构造方法的私有属性放开
                CommonUtil commonUtil = constructor.newInstance();
    
            } catch (Exception e) {
                e.printStackTrace();
            }

    预防反射攻击

    要避免通过反射来调用私有构造器这是行不通的,那么该如何做呢,这里有两种做法。

    • 当尝试使用构造方法new 对象时,直接抛出异常
    • 使用枚举,枚举类jvm底层保证了不可new。

    第一种:

    package com.fine.reflect.enhance;
    
    /**
     * 单例
     * volatile 双重校验
     *
     * @author finefine at: 2019-05-02 22:22
     */
    public class Singleton {
        private volatile static Singleton INSTANCE;
    
        private Singleton() {
            //如果已存在,直接抛出异常,保证只会被new 一次
            if (INSTANCE != null) {
                throw new RuntimeException("对象已存在不可重复创建");
            }
        }
    
        public static Singleton getInstance() {
    
            if (INSTANCE==null){
    
                //同步代码块
                synchronized (Singleton.class){
                    if (INSTANCE == null) {
                        INSTANCE = new Singleton();
                    }
                }
            }
            return INSTANCE;
        }
    }

    第二种:

    /**
     * 单例
     * 枚举
     *
     * @author finefine at: 2019-05-02 22:22
     */
    public enum  SingletonEnum {
        INSTANCE;
    }
    
    package com.fine.reflect.enhance;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    
    public class SingletonEnumTest {
    
        public static void main(String[] args) {
            SingletonEnum singletonEnum = SingletonEnum.INSTANCE;
    
            Class clazz = singletonEnum.getClass();
            try {
                Constructor<Singleton> constructor = clazz.getDeclaredConstructors()[0];
    
                //设置允许访问私有的构造器
                constructor.setAccessible(true);
                Singleton singleton1 = constructor.newInstance();
    
                if (singleton1 != null && singleton1.getClass().equals(singletonEnum.getClass())) {
                    System.out.println("通过反射构造除了对象");
                } else {
                    return;
                }
    
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
        }
    }

    静态内部类

    public class Singleton {
        private Singleton() {}
    
        public static Singleton getInstance() {
            return SingletonInstance.INSTANCE;
        }
    
        private static class SingletonInstance {
            private static final Singleton INSTANCE = new Singleton();
        }
    }
    这种方式跟饿汉式方式采用的机制类似,但又有不同。
      两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
      不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
      类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
    优点:避免了线程不安全,延迟加载,效率高。

    容器单例

    public class ContainerSingleton {
    
        private ContainerSingleton(){
    
        }
        private static Map<String,Object> singletonMap = new HashMap<String,Object>();
    
        public static void putInstance(String key,Object instance){
            if(StringUtils.isNotBlank(key) && instance != null){
                if(!singletonMap.containsKey(key)){
                    singletonMap.put(key,instance);
                }
            }
        }
    
        public static Object getInstance(String key){
            return singletonMap.get(key);
        }
    }

    这种方式实现的单例是线程不安全的。如果需要线程安全的可以使用HashTable但是HashTable每次存取都会加上同步锁,性能损耗比较严重。无法规避反射攻击和序列化破坏

    ThreadLocal “单例“

    这个单例严格意义上讲并不完全算是单例,它只能算在单个线程中的单例,也就是在同一个线程中的它是单例的。在全局上是非单例的;

    public class ThreadLocalInstance {
        private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal
                 = new ThreadLocal<ThreadLocalInstance>(){
            @Override
            protected ThreadLocalInstance initialValue() {
                return new ThreadLocalInstance();
            }
        };
        private ThreadLocalInstance(){
    
        }
        public static ThreadLocalInstance getInstance(){
            return threadLocalInstanceThreadLocal.get();
        }
    
    }

     运行结果:

     同步锁:以时间换空间

    threadLocal: 空间换时间

     枚举单例
    /**
     * @ClassName SingleThreadLazy
     * @Description 懒加载 枚举实现单例
     * @Author xinsen.liao
     * @Date 2020/7/1  10:27
     */
    public class SingleThreadEnumLazy {
    
    
        private SingleThreadEnumLazy() {
        }
    
        private enum InnerEnumInstance {
            INSTANCE;
            private SingleThreadEnumLazy singleThreadEnumLazy;
    
            private InnerEnumInstance() {
                singleThreadEnumLazy = new thread.SingleThreadEnumLazy();
            }
    
            private SingleThreadEnumLazy getInstance() {
                return singleThreadEnumLazy;
            }
        }
    
        public static SingleThreadEnumLazy getInstance() {
            return SingleThreadEnumLazy.InnerEnumInstance.INSTANCE.getInstance();
        }
    
        public static void main(String[] args) {
            IntStream.rangeClosed(1, 100).forEach(i -> new Thread(i + "-thread") {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "..." + SingleThreadEnumLazy.getInstance());
                }
            }.start());
        }
    
    }
     

    参考:

    JAVA设计模式总结之23种设计模式
    链接1:https://www.jianshu.com/p/2b45a08839d0
    链接2:https://www.jianshu.com/p/dd238dc20e22
     
     
  • 相关阅读:
    20145312 《信息安全系统设计基础》第13周学习总结
    20145312《信息安全系统设计基础》实验五 网络通信
    20145312 《信息安全系统设计基础》第12周学习总结
    20145312 GDB调试汇编堆栈过程分析
    20145312《信息安全系统设计基础》实验四 驱动程序设计
    20145312 《信息安全系统设计基础》第11周学习总结
    20145312 《信息安全系统设计基础》实验三 实时系统的移植
    20145312 《信息安全系统设计基础》第10周学习总结
    20145312 《信息安全系统设计基础》第9周学习总结
    20145209&20145309信息安全系统设计基础实验报告 (4)
  • 原文地址:https://www.cnblogs.com/personsiglewine/p/12971030.html
Copyright © 2011-2022 走看看