前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >23天读懂23种设计模式:单例模式(创建型)

23天读懂23种设计模式:单例模式(创建型)

作者头像
后台技术汇
发布2022-05-28 12:53:28
1870
发布2022-05-28 12:53:28
举报
文章被收录于专栏:后台技术汇

创建型模式是用来创建对象的模式,抽象了实例化的过程,帮助一个系统独立于其他关联对象的创建、组合和表示方式。

单例模式目的:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

winter

单例模式也是创建型的设计模式之一,本文是设计模式系列(共24节)的第2篇文章。

设计模式是基于六大设计原则进行的经验总结:《第一节:设计模式的六大原则

创建型设计模式共5种:

  • 单例模式(Singleton Pattern):一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
  • 工厂方法模式(Factory Pattern):在工厂方法模式中,工厂类成为了抽象类,实际的创建工作将由其具体子类来完成。
  • 抽象工厂模式(Abstract Factory):抽象工厂可以向客户提供一个接口,创建多个产品族中的产品对象,强调的是“对象系列”的变化。
  • 建造者模式(Builder Pattern):把构造对象实例的逻辑移到了类的内部,在类的外部定义了该类的构造逻辑,强调的是产品的构造过程。
  • 原型模式(Prototype Pattern):原型模式和工厂模式一样,同样对客户隐藏了对象创建工作具体的实现细节,通过复制一个现有的对象生成新对象。

单例模式是什么

单例模式(Singleton Pattern)可以说是整个设计中最简单的模式之一,且这种模式即使在没有看设计模式相关资料也经常在编码开发中。

因为在编程开发中经常会遇到这样一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,并需要提供一个全局访问此实例的点。

综上以及我们平常的开发中,可以总结一条经验,单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能。

普通模式(非线程安全)

普通模式的特点是:不允许外部直接创建,且对象是全局共享。

下面 例子1 是普通实现方法的单例模式,也是我们最常用的:

代码语言:javascript
复制
/**
 * 普通写法
 */
public class SingletonCase1 {
  private static SingletonCase1 singleton = null;
  public SingletonCase1() {
  }
  /**
   * 并发下会产生多个实例
   */
  public static SingletonCase1 getInstance(){
    if (singleton == null){
      singleton = new SingletonCase1();
    }
    return singleton;
  }
}

代码分析:

因此虽然在默认的构造函数上添加了私有属性private,也确实满足了懒加载,但是如果有多个访问者同时去获取对象实例,你可以想象很多人在抢厕所,就会造成多个同样的实例并存,所以是非线程安全的。

饿汉模式(线程安全)

饿汉式单例模式的特点是:类在加载时就直接初始化了实例。即使没用到,也会实例化,因此,它也是线程安全的单例模式。

下面 例子2 是饿汉模式:

代码语言:javascript
复制
/**
 * 饿汉式
 * 饿汉式的特点是:类在加载时就直接初始化了实例。即使没用到,也会实例化,因此,也是线程安全的单例模式。
 */
public class SingletonCase3 {
  /**类在加载的时候直接进行初始化*/
  private static SingletonCase3 singleton = new SingletonCase3();
  public SingletonCase3() {
  }
  /**
   * 对外暴露唯一接口
   * 提供单例对象
   */
  public static SingletonCase3 getInstance(){
    return singleton;
  }
}

懒汉模式(加锁&线程安全)

懒汉式单例模式的特点:对比普通模式,给方法加了排它锁,这是线程安全的写法;对比饿汉模式,全局对象只会在用到时才会进行初始化。

下面 例子3 是懒汉模式:

代码语言:javascript
复制
/**
 * 懒汉式,对比SingletonCase1,给方法加了排它锁,这是线程安全的写法。
 * 用到这个实例时才去调用方法实例化。但是,我们把整个方法都同步了,效率很低下,我们可以继续优化,只在创建实例的地方加上同步
 */
public class SingletonCase2 {
  private static SingletonCase2 singleton = null;
  public SingletonCase2() {
  }
  /**
   * 整个方法锁住了,效率较低
   * @return
   */
  public synchronized static SingletonCase2 getInstance(){
    if (singleton == null){
      singleton = new SingletonCase2();
    }
    return singleton;
  }
}

双重校验模式

双重校验模式的特点:考虑到多线程下的并发操作,全局对象使用了 volatile关键字 修饰,同时在对象初始化时进行加锁防止对象被其他线程重复创建。

此处可以参考《Java并发编程实战》的 第3章volatile的使用

下面 例子4 是双重校验模式:

代码语言:javascript
复制
/**
 * 双重非空判断,new对象前加一次锁。
 * volatile关键字,考虑的是,new关键字在虚拟机中执行时其实分为很多步骤,具体原因可以参考深入理解java虚拟机一书(考虑的是这个new关键字字节码执行时是非原子性的),而volatile关键字可以防止指令重排。
 */

public class SingletonCase4 {

  /**volatile防止指令重排*/
  private static volatile SingletonCase4 singleton;

  public SingletonCase4() {
  }

  /**
   * 只是在实例为空时才进行同步创建
   * 为什么做了2次判断?
   * A线程和B线程同时进入同步方法 getInstance
   * 然后都在1位置处判断了实例为null
   * 然后都进入了同步块2中
   * 然后A线程优先进入了同步代码块2中(B线程也进入了),然后创建了实例
   * 此时,如果没有3处的判断,那么A线程创建实例同时,B线程也会创建一个实例
   * 所以,还需要做2次判断
   * */
  public static SingletonCase4 getInstance(){
    if (singleton == null){
      synchronized (SingletonCase4.class){
        if (singleton == null){
          singleton = new SingletonCase4();
        }
      }
    }
    return singleton;
  }
}

内部类模式

内部类模式的特点是:由于静态内部类跟外部类是平级的,所以外部类加载的时候不会影响内部类,因此实现了懒加载。

下面 例子5 是内部类模式:

代码语言:javascript
复制
/**
 * 内部类
 * 优点:由于静态内部类跟外部类是平级的,所以外部类加载的时候不会影响内部类,因此实现了lazy loading, 同时也是利用静态变量的方式,
 * 使得INSTANCE只会在SingletonHolder加载的时候初始化一次,从而保证不会有多线程初始化的情况,因此也是线程安全的。
 */
public class SingletonCase5 {
  //静态内部类,懒加载:在被用到时才加载(根据内部类不会在其外部类被加载的同时被加载的事实)
  private static class SingletonCase5Holder{
    private static final SingletonCase5 singleton = new SingletonCase5();
  }
  public SingletonCase5() {
  }
  public static SingletonCase5 getInstance(){
    return SingletonCase5Holder.singleton;
  }
}

枚举类模式

枚举类模式的特点是:创建一个枚举类,封装一个对象,通过枚举类的私有构造器,强化了单例模式,且实现了懒加载,当唯一全局入口被调用才会初始化对象。

此处可以参考《Effective Java》的 第3点 以及 第4点

下面 例子6 枚举类模式:

代码语言:javascript
复制
/**
 * 枚举类
 * 本质就是:创建一个枚举类,封装一个对象,枚举类私有构造器中初始化对象
 */

public class SingletonCase6 {
  enum SingletonEnum {
    //懒加载,创建一个枚举对象,该对象天生为单例
    INSTANCE;
    private SingletonCase6 singleton;

    //私有化枚举的构造函数(强调不可外部实例化)
    private SingletonEnum() {
      singleton = new SingletonCase6();
    }

    public SingletonCase6 getSingleton() {
      return singleton;
    }
  }

  private SingletonCase6() {
  }

  public static SingletonCase6 getInstance(){

    getInstance();
  }
}

通过私有化构造器可以强化单例属性

总结

单例模式应该是最常见的设计模式之一了,它归类在设计模式创建型模式,用于解决对象创建的对象复用问题。

创建型模式还包括:原型模式、工厂模式、抽象工厂模式和建造者模式,待后续我们再细细解读。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后台技术汇 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例模式是什么
  • 普通模式(非线程安全)
  • 饿汉模式(线程安全)
  • 懒汉模式(加锁&线程安全)
  • 双重校验模式
  • 内部类模式
  • 枚举类模式
  • 总结
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档