zoukankan      html  css  js  c++  java
  • 设计模式-02.单例模式

    单例模式

    学习途径来自菜鸟教程:https://www.runoob.com/design-pattern/singleton-pattern.html

    单例模式(Singleton pattern),这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

    • 属于创建型模式,它提供了一种创建对象的最佳方式。
    • 在任何情况下都只有一个实例。
    • 单例类只能有一个实例。
    • 单例类必须自己创建自己的唯一实例。
    • 单例类必须给所有其他对象提供这一实例。

    **主要解决:**一个全局使用的类频繁地创建与销毁。

    **何时使用:**控制实例数目,节省系统资源的时候。

    **如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

    **关键代码:**构造函数是私有的。

    重 点

    1. 私有化构造器
    2. 了解线程对单例模式的影响,保证线程安全
    3. 延迟加载,避免类加载时就创建过多的无用单例从而造成内存浪费
    4. 防止序列化和反序列化破坏单例
    5. 防止反射对单例的破坏

    优点

    • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
    • 2、避免对资源的多重占用(比如写文件操作)。

    缺点

    没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

    饿汉式单例

    • 写法1:
    public class HungrySingleton{
        public static final HungrySingleton hungrySingleton = new HungrySingleton();
        
        //构造方法私有化
        private HungrySingleton(){}
        
        public static HungrySingleton getInstance(){return hungrySingleton;}
        
    }
    
    • 写法2:
    public class HungrySingleton{
        //加载顺序
        //先静态后动态
        //先上后下
        //先属性后方法
        public static final HungrySingleton hungrySingleton;
        
        static {
            hungrySingleton = new HungrySingleton()
        }
        
        //构造方法私有化
        private HungrySingleton(){}
        
        public static HungrySingleton getInstance(){return hungrySingleton;}
        
    }
    

    优点

    • 没有加锁,执行效率会提高。
    • 线程安全(基于 classloader 机制避免了多线程的同步问题)

    缺点

    • 类加载时就初始化,(某些情况下)浪费内存。

    懒汉式

    1. 线程不安全

    public class LazySimpleSingleton{
        public static LazySimpleSingleton instance;
        
        //构造方法私有化
        private LazySimpleSingleton(){}
        
        public static LazySimpleSingleton getInstance(){
            if(instance == null){
                //多线程情况下,可能有多个线程同时进入instance == null的条件,从而导致创建多个实例
                instance = new LazySimpleSingleton();
            }
            return instance;
        }
    }
    

    优点

    1. 延时加载,节省空间

    缺点

    1. 线程不安全

    2. 线程安全

    public class LazySimpleSingleton{
        public static LazySimpleSingleton instance;
        
        //构造方法私有化
        private LazySimpleSingleton(){}
        
        //加锁 syncronized 关键字
        //多线程下此处容易阻塞,其他线程在此处等待
        public synchronized static LazySimpleSingleton getInstance(){
            if(instance == null){
                instance = new LazySimpleSingleton();
            }
            return instance;
        }
    }
    

    优点

    1. 延时加载,节省空间
    2. 线程安全

    缺点

    1. 效率低

    3. 双检锁/双重校验锁(DCL)

    双检锁/双重校验锁(DCL,即 double-checked locking)

    public class LazyDoubleCheckSingleton{
        //volatile关键字,解决指令重排序问题
        public volatile static LazyDoubleCheckSingleton instance;
        
        //构造方法私有化
        private LazyDoubleCheckSingleton(){}
        
        public static LazyDoubleCheckSingleton getInstance(){
            //判断是否需要阻塞
            if(instance == null){
                //锁,多线程下可能会有部分线程进入此处
                synchronized(LazyDoubleCheckSingleton.class){
                    //判断是否需要创建实例
                    if(instance == null){
                        instance = new LazyDoubleCheckSingleton();
                    }
           		}
            }
            return instance;
        }
    }
    

    注:jdk1.5起

    优点

    1. 延时加载,节省空间
    2. 线程安全
    3. 效率高

    缺点

    1. 实现较复杂
    2. 代码可读性较低

    4. 登记式/静态内部类

    /**
    * 内部类加载方式及顺序:
    *       ClassPath: LazyStaticInnerSingleton.class
    *                  LazyStaticInnerSingleton.class$LazyHolder.class
    * 优点:写法优雅,利用了java本身语法的特点,性能高,避免了内存浪费
    * 缺点:
    */
    public class LazyStaticInnerSingleton {
        //构造方法私有化
        private LazyStaticInnerSingleton(){
            //防止被反射破坏
            //if(LazyHolder.INSTANCE != null){
            //    throw new RuntimeException("不允许非法访问")
            //}
        }
        
        public static LazyStaticInnerSingleton getInstance(){
            //程序中调用getInstance时发现调用了LazyHolder内部类,
            //去加载LazyStaticInnerSingleton.class$LazyHolder.class
            //执行完LazyStaticInnerSingleton.class$LazyHolder.class的逻辑之后返回结果
            return LazyHolder.INSTANCE;
        }
        
        private static class LazyHolder {
            private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
        }
    }
    

    优点

    1. 延时加载,节省空间
    2. 线程安全

    缺点

    1. 能够被反射破坏

    5. 枚举式单例

    枚举式单例是注册式单例的一种。

    public enum EnumSingleton {
        INSTANCE;
        
        private  Object ojb;
        
        public Object getObj(){return obj;}
        
        public void setObj(Object obj){ this.ojb = obj;}
    
        public static EnumSingleton getInstance(){return INSTANCE;}
    }
    

    优点

    1. 线程安全

      同饿汉式类似,在声明时便初始化好,不存在线程问题

    2. 不被反射破坏

      public class ReflectTest{
          Class<?> clazz = LazyStaticInnerSingleton.class;
          //枚举类Enum没有无参的构造方法,通过访问构造方法获取实例
          Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
          //构造方法为私有,设置“强吻”
          c.setAccessible(true);
          //此行会报错,Cannot reflectively create enum objects(不能通过反射创建枚举对象)
          Object instance1 = c.newInstance();
          System.out.println(instance1);
      }
      
      Construcor.java
          
      if(clazz.getModifiers() & Modifier.ENUM != 0)
       throw new IllegalArgumentException("Cannot reflectively create enum objects");
      

    缺点

    1. 不是延时加载
    2. 不适合大批量创建单例的情况

    6. 容器式单例

    public class ContainerSingleton{
       
       private ContainerSingleton(){};
       
       public static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
       
       public static Object getInstance(String className){
           Object instance = null;
           if(!ioc.containsKey(className)) {
               synchronized(ioc){
                   
                   if(!ioc.containsKey(className)){
                       try{
                           instance = Class.forName(className).newInstance();
                           ioc.pub(className, instance);
                       }catch(Exception e){
                           e.printStackTrace();
                       }
                       return instance;
                   }
            	}
           }else{
               //此处简单实现,这里还应该判断 ioc.get(className) == null
               //或者ioc.get(className) == new Object()的情况,
               //这两种情况下应该都需要重新ioc.pub(className, instance)
           	return ioc.get(className);   
           }
       }
    }
    

    反射破坏单例

    public class ReflectTest{
        Class<?> clazz = LazyStaticInnerSingleton.class;
        //通过访问无参的构造方法获取实例
        Constructor c = clazz.getDeclaredConstructor(null);
        //构造方法为私有,设置“强吻”
        c.setAccessible(true);
        Object instance1 = c.newInstance();
        
        Object instance2 = c.newInstance();
    
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance1 == instance2);
    }
    

    解决:可参考5.枚举式单例,或4.登记式单例代码中注释部分,对单例的私有方法进行判断改造

    序列化破坏单例

    public class SeriableSingleton implements Serializable {
    
        //序列化
        //把内存中对象的状态转换为字节码的形式
        //把字节码通过IO输出流,写到磁盘上
        //永久保存下来,持久化
       
        //反序列化
        //将持久化的字节码内容,通过IO输入流读到内存中来
        //转化成一个Java对象
    
        public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
        private SeriableSingleton(){}
    
        public static SeriableSingleton getInstance(){
            return INSTANCE;
        }
    
        //防止单例模式被序列化破坏,此处也用到了桥接模式
        private Object readResolve(){ return INSTANCE;}
         
    

    ThreadLocalSingleton

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

    经验之谈

    • 一般情况下,不建议使用第 1 种—(线程不安全)和第 2 种—(线程安全)懒汉方式。
    • 建议使用饿汉方式。
    • 只有在要明确实现 lazy loading 效果时,才会使用第 4 种–登记方式。
    • 如果涉及到反序列化创建对象时,可以尝试使用第 5 种—枚举方式。
    • 如果有其他特殊的需求,可以考虑使用第 3 种—双检锁方式。
  • 相关阅读:
    经典卷积神经网络算法(4):GoogLeNet
    经典卷积神经网络算法(3):VGG
    经典卷积神经网络算法(2):AlexNet
    经典卷积神经网络算法(1):LeNet-5
    卷积神经网络入门篇
    人工神经网络实践
    Python操作Oracle数据库:cx_Oracle
    源码剖析@ApiImplicitParam对@RequestParam的required属性的侵入性
    实战SpringCloud通用请求字段拦截处理
    Elasticsearch系列---生产集群部署(下)
  • 原文地址:https://www.cnblogs.com/healkerzk/p/13059915.html
Copyright © 2011-2022 走看看