zoukankan      html  css  js  c++  java
  • 经典设计模式之:单例设计模式

    单列设计模式,解决了什么问题?

    单例设计模式的配套技术:

    懒汉式(延迟加载/懒加载)(lazy load)

    优势是:节省资源,

    饿汉式

     优势是:线程安全

    IoDH技术:

    通过静态内部类在java中的语言特性解决了线程同步,和资源浪费等问题

    在java语言中,单例带来了两大好处:

    1.对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象而言,是非常可观的一笔系统开销。

    2.由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。

    单例模式的核心在于通过一个接口返回唯一的对象实例,一个简单的单例实现如下:

    废话少说来片代码尝尝鲜:

    经常用的饿汉式写法

    class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
    return instance;
      }
    }

    注意代码的蓝色部分,首先单例类必须要有一个private访问级别的构造函数,只有这样,才能确保单例不会在系统中的其他代码内被实例化,这点是相当重要的;其次,instance成员和getInstance()方法必须是static的。

    这种单例的实现方式非常简单,而且十分可靠,它唯一的不足仅是无法对instance实例做延迟加载,假如单例的创建过程很慢,而由于instance成员变量是static定义的,因此在JVM加载单例类时,单例对象就会被建立,如果此时,这个单例类在系统中还扮演其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,根本就不会管是否被用到。比如单例String工厂,用于创建一些字符串(该类既用于创建单例Singleton,又用于创建String对象)。

    //饿汉式写法:

    public class Singleton {
        private Singleton() {
            System.out.println("Singleton  is  create"); // 创建单例的过程可能会比较慢
        }
     
        private static Singleton instance = new Singleton();
     
        private static Singleton getInstance() {
            return instance;
        }
         
        public static void CreateString(){       //这是模拟单例类扮演其他角色
            System.out.println("createString in Singleton");
        }
    }

    上面代码的意思就是,假如单例类里面有getInstance以外的其它静态方法,跟单例没啥关系的方法,如果使用了Singleton.CreateString()这种调用,就会自动创建Singleton这个类实例(虽然private构造已经防止了你人为去new这个单例),这是开发人员不愿看到的,我运行下面代码(此代码调用了上面代码的createString()方法)进行展示:

    结果是:

    对象仍然被隐形的实例化了(静态代码块上有实例化,所以只要实现这个类中的方法,静态代码快会首先执行)

    改良:

     实例化放在私有静态方法中,并且加以线程同步控制

    //单例设计模式基类
    public class Singleton {
        //先私有构造方法
        private Singleton() {
            System.out.println("Singleton is create...");//外部构造相较消耗时间
        };
    
        //这句话的作用是:保证所有调用这个类的Singleton类的对象是一致的
        private static Singleton instance = null;
        
        
        
        //创建外部调用接口(添加static否则无法加载)
        private  static synchronized Singleton getInstance() {
            if(instance == null) 
                System.out.println("getInstance is create...");
                instance = new Singleton();
                return instance;
        }
        
        //模拟单例类扮演其他角色
        public static void CreatString() {
            System.out.println(" CreatString is create...");
        }
    }

    测试:

    结果:

    显而易见,这次改良主要是从JVM加载类的原理上进行改良的,上面的代码在初始化类的时候给instance赋予null,确保系统启动的时候没有额外的负载,其次,在getInstance方法中加入判断单例是否存在,不存在才new。但是getInstance()方法必须是同步的,

    否则多线程环境下,可能线程1正在创建单例时,线程2判断单例instance为空,就会创建多个实例。

    但是上面的写法,存在性能问题,在多线程下,它的耗时远远大于第一种单例。以下代码就说明了此问题:

     测试:

    开启五个线程同时完成以上代码的运行,使用第一种单例耗时0ms,而使用第二种单例耗时390ms,性能至少相差两个数量级。

    为了线程安全使用了同步关键字反而降低了系统性能,为了解决这个问题,

    继续改进:

    public class Singleton {
        private Singleton() {
            System.out.println("LazySingleton  is  create");
        }
    
        public static class SingletonHolder {
            private static Singleton instance = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    
    }

    测试:

    没错,这个是最终版,也是线程安全的,静态内部类实现单例,除了effctive java中阐述的枚举单例实现,这种方法是目前最好的实现方式。这种更好方式的被称之为Initialization Demand Holder (IoDH)的技术

    在这个实现中,用内部类来保护单例,当Singleton类被加载时,内部类不会被初始化,所以可以确保Singleton类被载入JVM时,不会初始化单例类,当getInstance方法被调用时,才会加载SingleHolder,从而初始化instance,同时,由于实例的建立是在类加载时完成的,故天生对多线程友好,getInstance()方法也不需要使用synchronized修饰,因此,这种实现能兼顾前两种写法的优点(延迟加载,非同步)。

    JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面出现的问题中的问题。此外该方法也只会在第一次调用的时候使用互斥机制,这样就解决了3.1中的低效问题。最后instance是在第一次加载SingletonContainer类时被创建的,而SingletonContainer类则在调用getInstance方法的时候才会被加载,因此也实现了惰性加载。

    通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH) 

     最后,在极端情况下,序列化和反序列化可能会破坏单例,一般来说不多见,如果存在就要多加注意,此时可以加入以下代码:

    private Object readResolve() {
            return instance;
        }

    参考资料:

    https://blog.csdn.net/u012221046/article/details/52440720

    https://www.cnblogs.com/jingpeipei/p/5771716.html

    <<设计模式Java版>>_刘伟

  • 相关阅读:
    docker一些基本操作
    Error requesting socket: exit status 255(一个很不错的解决办法)【转】
    十五周至十八周的任务进度
    7月24号day16总结
    7月23号day15总结
    7月22号day14总结
    7月21号day13总结
    7月20号day12总结
    7月19日day11总结
    7月18号day10总结
  • 原文地址:https://www.cnblogs.com/YangGC/p/8759980.html
Copyright © 2011-2022 走看看