2021-07-01

单例模式

是什么

创建对象的一种方式,该方式保证用户在程序运行阶段只创建一个该类实例。

做什么、为什么

适用于那些只能单实例的场景

  1. 日志收集:这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  2. 各种池管理技术,例如数据库连接池,线程池等;数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
  3. 配置文件读取,同日志类似,是共享的资源,保证只有一个实例访问。

如何实现

  1. 双重检查锁

    public class Singleton{
        private volatile Singleton singleton;
        
        public static Singleton getInstance(){
            if(singleton == null){
                synchronized(Singleton.class){
                    if(singleton == null){ 
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    
    • 为什么要双重锁

      • 首先第一层判断,是为了减少线程争抢锁资源的次数,当有线程创建对象成功后,其余线程就不需要进入synchronized代码块了。
      • 第二层判断,是为了保证对象不会重复创建,当线程1还未创建对象成功时,此时线程2进入锁等待,当线程1创建成功后,线程2获取到锁,进入第二层判断,此时,判断不通过,不创建对象。
    • 为什么要加volatile

      • 我们知道 被volatile修饰的变量其内存是可见的,也就是说每个线程所访问的该变量都是从主存内获取的,不存在内存屏障;并且最重要的是防止指令重排序,jvm在加载字节码文件时,会优化相关的指令,从而对指令进行重新排序。加了volatile修饰的变量不会被jvm重新排序。

      • new Singleton()时,实际上jvm执行了三个主要指令 分别为

        1. 为对象分配内存

        2. 对象初始化,

        3. 将内存地址指向堆中对象

          假设不加volatile修饰,可能会存在下面的场景,线程1创建对象时,jvm将第二步和第三步颠倒过来了,此时线程2进入第一个判断,判断当前对象不为null(对象已经有内存地址了,并且指向了堆中的未进行初始化的对象),返回对象使用,但是此时对象还没有执行第二步初始化,所以会导致线程2使用了一个未初始化对象。

  1. 静态内部类持有外部类对象,

    该种情况满足了延迟加载和线程安全

    public class Singleton{
        private  Singleton{}
        
        private static class InnerSingleton{
            private static Singleton singleton = new Singleton()
        }
        
        public static Singleton getInstance(){
            return InnerSingleton.singleton;
        }
    }
    

    延迟加载原理:类在5种情况下会被初始化

    • new对象
    • 反射Class.forName(xxx.class);
    • 调用类的静态变量或者静态方法 (此种情况就是调用getInstance()静态方法才会触发加载)
    • 初始化类时,该类有直接父类,先加载父类
    • 用户指定的具有main方法的类

    线程安全原理:jvm在加载类时,保证了类的cinit方法是同步的,同一时刻,同一类加载器下只有一个线程加载类。

  2. 两种方案比较

    静态内部类缺点:不能传参,双重检查锁可以传参

    双重检查锁缺点:加了锁,效率会低点

    结论:当无参数时,使用静态内部类,有参数时选择双重检查锁

知识延伸

synchronized

volatile 关键字

  • 防止jvm指令重排序
  • 线程见的内存可见性

jvm 类加载机制

你可能感兴趣的:(2021-07-01)