• 关于单例模式的N种实现方式


      在开发中经常用到单例模式,单例模式也算是设计模式中最容易理解,也是最容易手写代码的模式,所以也常作为面试题来考。所以想总结一下单例模式的理论知识,方便同学们面试使用。

      单例模式实现的方式只有两种类型,一种是饿汉式(类加载时就初始化)、一种是懒汉式(类加载时不初始化)。饿汉式没什么可讲究的因为它既简单也线程安全,如果条件允许一般我们都会直接用饿汉式;唯独比较麻烦的是懒汉式,考虑到线程安全,使用懒汉式的单例模式,就有多种实现方式了。

      一、饿汉式

      如上所述,饿汉式很简单,实现方式如下:

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

      这种单例模式,很简单而且还安全,可以说近乎完美。它唯一的缺陷就是,这种实现方式就无法适用于单例还需要一定的配置或者传参的场景。

      二、懒汉式

      懒汉式的单例模式,由于要考虑到线程安全的情况,有多种实现方式。

      0、如下的实现方式是一种不安全的实现方式

    public class Singleton {
        private static Singleton instance;
        private Singleton (){}
    
        public static Singleton getInstance() {
         if (instance == null) {
             instance = new Singleton();
         }
         return instance;
        }
    }

      之所以把定义为编号0,就是除非你非常确定没有多线程的场景,因为当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。所以绝大部分场景下,这种实现方式都是不应该使用。

      1、低性能的懒汉式

    public static synchronized Singleton getInstance() {
        private static Singleton instance;
        private Singleton (){}
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

      这种实现方式,解决了多线程实例问题,但是由于直接在方法上使用synchronized关键字,即类锁同步,使得只能使用每次调用都要进行同步操作,性能比较低。

      2、双重检验锁的懒汉式

    public class Singleton {
      private volatile static Singleton instance;
    
      private Singleton() {
      }
    
      public static Singleton getSingleton() {
        if (instance == null) { // null 检测
          synchronized (Singleton.class) { // 同步检测
            if (instance == null) {
              instance = new Singleton();
            }
          }
        }
        return instance;
      }
    
    }

      我们看到最多的情况就是没有加volatile的情形,其实你不加这个关键字,面试你的人应该也不会纠结于这个,因为99.99%的情况都是好的(要知道那些提供后台服务的,一般只会保证99%可靠性……)。之所以加是因为JVM存在指令重排的优化,导致

    new Singleton() 不是一个原子操作行为,new Singleton() 这句话执行的过程大概分为以下abc步骤:

      a, 给 instance 分配内存;

      b, 调用 Singleton 的构造函数来初始化成员变量;

      c, 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了);

      JVM 的即时编译器中存在指令重排序的优化,执行顺序可能是 abc 也可能是 acb。如果是acb,在执行到c,被线程X抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程X会直接返回 instance,使用一个没有初始化的实例产生错误是必然的。那为什么要加volatile呢?一般我们只用到volatile的可见性功能,即对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入,而且经常用于解决同步问题。其实volatile还有一个重要的功能——禁止指令重排优化,也就是volatile标记的变量不会被编译器优化。如上所示,加上volatile以后,读操作一定会发生在abc或者acb之后,并且这个顺序是固定。

      3、静态内部类实现懒汉式

    public class Singleton {  
        private static class InternalSingleton {  
            private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
            return InternalSingleton.INSTANCE; 
        }  
    }

      由于InternalSingleton 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷。

      4、通过枚举实现懒汉式
      通过枚举写单例超级简单,什么都不用想,这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。

    public enum Singleton {
        INSTANCE;
        public void show(int age) {
            System.out.println("this is show function -> " + age);
        }
    }

      我们可以通过Singleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。可能由于平时我们使用枚举都是为了表示类别,大家都很少使用这种方式去写单例模式。

  • 相关阅读:
    09.移动先行之谁主沉浮----控件之轮流轰炸——高级控件
    08.移动先行之谁主沉浮----控件之轮流轰炸——常用控件
    备份和还原 第三篇:master 数据库的备份和还原
    备份和还原 第二篇:数据库还原
    备份和还原 第一篇:开始数据库备份
    分页实现:Offset-Fetch
    RowVersion 用法
    TSQL 分组集(Grouping Sets)
    MongoDB 存储引擎:WiredTiger和In-Memory
    MongoDB 安全和访问权限控制
  • 原文地址:https://www.cnblogs.com/wytings/p/5429106.html
Copyright © 2020-2023  润新知