单例模式 之 写法大全

2022-02-05  本文已影响0人  程就人生

单例模式不仅仅是面试官最爱考核的内容之一,也是程序员们在实际开发中经常用到的设计模式之一,所以它是我们的必备技能之一。说到单例模式,你能想到哪些问题呢?

  1. 单例模式的特点,请说说?

  2. 单例模式,主要有哪些应用场景,你还记得吗?

  3. 单例模式有哪些写法,你还记得多少?

  4. 单例模式是线程安全的吗?

先上一张思维导图:

图片

单例模式:是一种创建型模式,不为别人创建对象,只为自己创建对象,以提供一个全局的唯一的对象实例。这个全局是指在一个容器里的全局。一个启动的项目只保持一个该对象的实例。

使用场景:生产唯一序列号,Web里的计算器,还要其他比较消耗资源的开销,比如数据库连接池,I/O操作等等。

缺点:没有接口,不能继承,与单一职责原则冲突。

背诵再多概念,也不及敲上一段代码。

1. 单例模式 之 懒汉模式一,线程不安全;

/**
 * 懒汉式 ,线程不安全
 * 延迟加载
 * @author 程就人生
 * @Date 2022/1/25
 */
public class Singleton {  
  
  /**
   * 静态的实例变量,实例化后不会被改变
   */
    private static Singleton instance;  
    
    /**
     * 私有的构造函数
     */
    private Singleton (){}  
  
    /**
     * 没加同步锁,不支持多线程,线程不安全
     * @return
     */
    public static Singleton getInstance() {  
      // 延迟加载
      if (instance == null) {  
          instance = new Singleton();  
      }  
      return instance;  
    }  
}
图片

懒汉模式有两种,一种是线程不安全的,一种是线程安全的。

2. 单例模式 之 懒汉模式二,线程安全;

该实现方式,增加了同步锁,但会对性能有影响。

/**
 * 懒汉式 ,线程安全
 * 延迟加载
 * @author 程就人生
 * @Date
 */
public class Singleton {  
  
  /**
   * 静态的实例变量,实例化后不会被改变
   */
    private static Singleton instance; 
    
    /**
     * 私有的构造函数
     */
    private Singleton (){}  
    
    /**
     * 使用了synchronized 关键字,线程安全,支持多线程
     * @return
     */
    public static synchronized Singleton getInstance() {  
      // 延迟加载
      if (instance == null) {  
          instance = new Singleton();  
      }  
      return instance;  
    }  
}

3. 单例模式 之 饿汉模式一;

图片
/**
 * 饿汉式 1
 * 线程安全,非延迟加载
 * 不管是否使用,先进行了实例化,所以称之为饿汉式
 * @author 程就人生
 * @Date 2022/1/25
 */
public class Singleton {  

  /**
   * 先静态,后动态
   * 先属性,后方法
   * 先上后下
   */
    private static Singleton instance = new Singleton();  
    
    /**
     * 私有的构造函数
     */
    private Singleton (){}  
    
    /**
     * 直接返回
     * @return
     */
    public static Singleton getInstance() {  
      return instance;  
    }  
}

4. 单例模式 之 饿汉模式二;

/**
 * 饿汉式 静态块单例模式
 * 线程安全,非延迟加载
 * @author 程就人生
 * @Date 2022/1/25
 */
public class Singleton{
  private static final Singleton instance;
  /**
   * 静态代码块,公共内存区域
   */
  static {
    instance = new Singleton();
  }
  /**
   * 私有的构造函数
   */
  private Singleton (){}  
    
  /**
   * 直接返回
   * @return
   */
  public static Singleton getInstance() {  
     return instance;  
  }    
}

5. 单例模式 之 双检锁/双重校验锁 (DCL double-checked locking)

该实现方式,是对懒汉安全模式的升级,提高了性能。

图片
/**
 * 双检锁/双重校验锁 (DCL double-checked locking)
 * 线程安全,延迟加载
 * @author 程就人生
 * @Date 2022/1/25
 */
public class Singleton {  
  
  /**
   * 静态的实例变量,实例化后不会被改变
   * 还使用了 volatile 关键字
   */
    private volatile static Singleton singleton; 
    
    /**
     * 私有的构造函数
     */
    private Singleton (){}  
    
    /**
     * 在获取实例的方法内部使用了同步锁
     * 同步锁是在getInstance方法内,阻塞发生在getInstance方法上,而不是类上;
     * 业务逻辑不复杂,用户感知不到
     * @return
     */
    public static Singleton getInstance() {  
      if (singleton == null) {  
          synchronized (Singleton.class) {  
          if (singleton == null) {  
            // new一个对象做的三件事
            // 1.分配内存给对象
            // 2.初始化对象
            // 3.设置singleton指向刚分配的内存地址
              singleton = new Singleton();  
          }  
          }  
      }  
      return singleton;  
    }  
}

6. 单例模式 之 登记式/静态内部类;

该实现方式,是对 双检锁/双重校验锁 方式的升级;兼顾了饿汉式单例模式的内存浪费和同步锁的性能问题。

图片

/**
 * 登记式/静态内部类
 * 线程安全,延迟加载
 * 兼顾饿汉单例模式的内存浪费问题和同步的性能问题
 * @author 程就人生
 * @Date 2022/1/25
 */
public class Singleton {  
    
    /**
     * 私有的构造函数
     * 使用Singleton的时候,默认会先初始化内部类
     */
    private Singleton (){}  
    
    /**
     * 获取实例
     * static 使单例的空间共享,保证这个方法不会被重写、重载
     * @return
     */
    public static final Singleton getInstance() {  
      // 返回结果以前,一定会先调用加载内部类
      return SingletonHolder.INSTANCE;  
    }  
    
    /**
   * 静态内部类,默认不加载
   */
    private static class SingletonHolder {  
      private static final Singleton INSTANCE = new Singleton();  
    }  
}

7. 单例模式 之 枚举式;

大师们都推崇的写法,最简易的写法。

图片

/**
 * 枚举式
 * 线程安全,非延迟加载
 * @author 程就人生
 * @Date 2022/1/25
 */
public enum Singleton { 
  
    INSTANCE;  
  
  public static Singleton getInstance() {  
      return INSTANCE;  
    }
}

这些单例模式真的是安全的吗,口说无凭,来一个简单的多线程验证一下吧:

/**
 * 一个简单的多线程
 * 用多线程测试单例模式的线程安全
 * @author 程就人生
 * @Date
 */
class Thread1 extends Thread{
  // 线程名称
  private String name;
  
    public Thread1(String name) {
       this.name=name;
    }
  public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(name + "运行  :  " + i);
            // 输出单例模式对象的地址

            System.out.println(Singleton.getInstance().toString());
            try {
              //随机休眠
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }       
  }
}

在main方法里调用;

public static void main(String[] argo){    
    Thread1 mTh1=new Thread1("A");
    Thread1 mTh2=new Thread1("B");
    mTh1.start();
    mTh2.start();
} 
  1. 单例模式 之 懒汉模式一,线程不安全,测试结果:

A运行  :  0
B运行  :  0
com.example.Singleton@b9098ae
com.example.Singleton@12b0f150
B运行  :  1
com.example.Singleton@b9098ae
B运行  :  2
com.example.Singleton@b9098ae
A运行  :  1
com.example.Singleton@b9098ae
A运行  :  2
com.example.Singleton@b9098ae

第二个地址和其他地址不一样,没有实现实例单一,因此是线程不安全的。

2. 单例模式 之 懒汉模式二,线程安全,测试结果:

A运行  :  0
B运行  :  0
com.example.Singleton@790d3283
com.example.Singleton@790d3283
B运行  :  1
A运行  :  1
com.example.Singleton@790d3283
A运行  :  2
com.example.Singleton@790d3283
com.example.Singleton@790d3283
B运行  :  2
com.example.Singleton@790d3283

查看输出的实例地址都是一样的,保证了单一实例,因此是线程安全的。

其他的就不一一验证了,请读者自行验证。

上一篇下一篇

猜你喜欢

热点阅读