• 单例模式---singleton


     在Java这这门语言里面,它的优点在于它本身的可移植性上面,而要做到可移植的话,本身就需要一个中介作为翻译工作,以达到本地和Java的统一,但是就这点而言就相当的消耗资源,所以就Java程序员需要不断的去优化自己的代码。今天所研究的单例模式就是在这样的条件下产生的,

        所谓单例模式,就是只有一个实例,在堆里面只有一个。假如我们的实例,就需要一个,但是会多次用到,这样的话就会出现很尴尬的问题。

        比如:

      1. Windows的TaskManager(任务管理器)就是很典型的只需要一个实例,
      2. Windows的Recycle(回收站)在系统中,回收站只维护一个实例
      3. 在我们的项目里面,会常常使用到读取配置文件的类,
      4. 网站的计数器,一般也采用单例模式,否则难以实现同步
      5. 数据库连接池设计一般也采用单例模式,因为数据库连接是一种数据库资源
      6. 操作系统的文件系统,因为一个操作系统只能有一个文件系统
      7. Application 也是单例模式
      8. Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
      9. servlet编程中,每个Servlet也是单例的
      10. 在Spring MVC/struts 1 中,所使用到的控制对象也是单例的

        在上述里面我们了解到,单例模式在我们项目中,几乎是天天出现,所以在这里,我们仔细研究一下,这种设计模式的怎么实现最好(说到实现,它的实现我们大多数人只知道有两种,而还有三种模式知道的人不是很多,以及利用反序列化,反射漏洞去强制解除单例)

    •   饿汉模式
      •   使用static属性来保持对象的单例模式,但是必须在类加载的时候加载,所以没有延迟实例化的性能
         *             而在得到实例的对象中,没有对资源的同步锁,所以调用效率高
         *             而使用JVM类加载器,JVM底层天然是线程安全的,
      • 优点:线程安全调用率较高
      • 缺点:不能延迟加载
        复制代码
         1 public class Eager_Singleton {
         2 
         3     private static Eager_Singleton singleton = new Eager_Singleton();
         4     
         5     private Eager_Singleton(){}
         6     
         7     public static Eager_Singleton newInstance(){
         8         return singleton;
         9     }
        10 }
        复制代码
    •   懒汉模式 
      •   依旧使用static属性来保持对象的单例模式,但是在方法里面new出来,当我们去调方法的时候,再去加载,就是懒人,懒得一上来就加载,但是就因为在方法里面实例化,所以我们要在该方法上面使用锁的概念来对他进行同步处理,如果不加锁,那我们在A线程里面去掉用它和在B线程里面去调用它他就不是一个概念了
        •  优点:可以延时加载,并且线程是安全的
        • 缺点:就是方法实现了同步,所以资源的利用率比较低。
    复制代码
    /**
     * 懒汉单例模式
     * @author 刘酸酸
     *
     */
    public class Sluggard_Singleton {
    
        private static Sluggard_Singleton singleton = null;
        
        private Sluggard_Singleton(){}
        
        public synchronized static Sluggard_Singleton newInstance(){
            if(singleton == null){
                singleton = new Sluggard_Singleton();
            }
            return singleton;
        }
    }
    复制代码
    • 双重检测式
      • 将同步块内部下方的代码放至到IF内部
      • 所面临的问题:由于编译器优化等原因JVM底层内部模型的原因,偶尔会出问题,不建议使用。

         

    复制代码
    /**
     * 双重检查锁实现单例模式
     * @author 刘酸酸
     *
     */
    public class DoubleCheck_Singleton { 
    
      private static DoubleCheck_Singleton instance = null; 
    
      public static DoubleCheck_Singleton getInstance() { 
        if (instance == null) { 
          DoubleCheck_Singleton sc; 
          synchronized (DoubleCheck_Singleton .class) { 
            sc = instance; 
            if (sc == null) { 
              synchronized (DoubleCheck_Singleton .class) { 
                if(sc == null) { 
                  sc = new DoubleCheck_Singleton (); 
                } 
              } 
              instance = sc; 
            } 
          } 
        } 
        return instance; 
      } 
    
      private DoubleCheck_Singleton () { 
    
      } 
        
    }
    复制代码
    • 静态内部类方式实现

      • 该方式是结合懒汉式和饿汉式的优点,

         

     
    /**
     * 测试静态内部类实现单例模式
     * 这种方式:线程安全,调用效率高,并且实现了延时加载!
     * @author 刘酸酸
     *
     */
    public class StaticInnerClass{
        
        private static class StaticinnerClass{
            private static final StaticInnerClass instance = new StaticInnerClass();
        }
        
        private StaticInnerClass(){
        }
        
        //方法没有同步,调用效率高!
        public static StaticInnerClass StaticInnerClassgetInstance(){
            return StaticinnerClass.instance;
        }
        
    }
     
    • 枚举实现
      • 在Java的JVM里面就是一种天然的单例模式,那就是枚举类型,如果使用枚举类型的话会受到JVM底层的保护
        • 比如使用反序列化,和使用反射是不能打破这个原则的

      • 优点:上诉

      • 缺点:不能延迟加载

        

    复制代码
    /**
     * 测试枚举式实现单例模式(没有延时加载)
     * @author 刘酸酸
     *
     */
    public enum Enum_Singleton{
        
        //这个枚举元素,本身就是单例对象!
        INSTANCE;
        
        //添加自己需要的操作!
        public void xxxxx(){
        }    
    }
    复制代码
    • 如何防止反序列化,反射破环单例模式(枚举除外)
      • 在反射中如果我们调用了私有的构造器的话,我们就会抛异常,但是会有人跳过合法检测,这样是可以访问到我们的私有构造器的
    复制代码
    import java.lang.reflect.Constructor;
    
    /**
     * 测试反射和反序列化破解单例模式
     * @author 刘酸酸
     *
     */
    public class Main{
        
        public static void main(String[] args) throws Exception {
            
            //通过反射的方式直接调用私有构造器
            Class<Singleton> clazz = (Class<Singleton>) Class.forName("com.suansuan.singleton.Singleton");
            Constructor<Singleton> c = clazz.getDeclaredConstructor(null);
            //跳过合法检查
            c.setAccessible(true);
            Singleton s1 = c.newInstance();
            Singleton s2 = c.newInstance();
            System.out.println(s1);
            System.out.println(s2);
      }
    }
    复制代码
      • 反序列化时,我们也不能得到一个对象,所以,下述代码解决这两种问题
        复制代码
        /**
         * 测试懒汉式单例模式(如何防止反射和反序列化漏洞)
         * @author 刘酸酸
         *
         */
        public class Singleton implements Serializable {
            //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
            private static Singleton instance;  
            
            private Singleton (){ //私有化构造器
                //反射时,我们作如下操作既可以规避,跳过合法检测的非法访问
                if(instance!=null){
                    throw new RuntimeException();
                }
            }
            
            //方法同步,调用效率低!
            public static  synchronized Singleton getInstance(){
                if(instance==null){
                    instance = new Singleton ();
                }
                return instance;
            }
            
            //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
            private Object readResolve() throws ObjectStreamException {
                return instance;
            }
            
        }
        复制代码

    总结:使用枚举去替代饿汉式,使用静态内部类去替代懒汉式

  • 相关阅读:
    axios核心技术---1.HTTP相关
    Leetcode刷题
    Java入门13---Optional处理null对象
    Java入门12---Stream
    谈谈前后端分离及认证选择
    react监控props的变化
    如何使用懒加载
    CSS文字超出省略
    React的slot插槽
    React 的函数组件和类组件中的props
  • 原文地址:https://www.cnblogs.com/dry0515/p/5817828.html
Copyright © 2020-2023  润新知