设计模式:单例模式

单例模式

Singleton Design Pattern, 一个类只允许创建一个对象(或者实例)

  1. 可以用来解决资源访问冲突的问题,只对外提供一个操作句柄
  2. 表示全局唯一类:比如系统配置信息类、

实现

饿汉式

在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例

public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static final IdGenerator instance = new IdGenerator(); // 类加载时创建实例
  // 私有构造器
  private IdGenerator() {}
  // 获取对象实例的静态方法
  public static IdGenerator getInstance() {
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}
懒汉式

支持延迟加载。为了保证线程安全,这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。


public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private IdGenerator() {}
  // 获取实例静态方法,每次调用都加锁,保证并发安全。方法第一次被调用时才执行创建对象逻辑
  public static synchronized IdGenerator getInstance() {
    if (instance == null) {
      instance = new IdGenerator();
    }
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}
双重检测

既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。

public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private IdGenerator() {}
  public static IdGenerator getInstance() {
  	// 第一重检查:是否已经创建了实例,如果没有才会执行加锁逻辑
    if (instance == null) {
      synchronized(IdGenerator.class) { // 此处为类级别的锁
      	// 第二次检查:是否已经创建了实例,如果还没有才会执行创建对象逻辑
        if (instance == null) {
          instance = new IdGenerator();
        }
      }
    }
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

jdk1.5 (低版本jdk)指令重拍问题,高并发创建实例时,如果 instance 被一个线程创建,并赋值给了 instance 成员,但是测试还没有初始化对象,其他线程使用会出问题

  • 本质:对象创建和初始化不是原子操作
  • 解决:加 volatile 修饰
    高版本 JDK 内部实现中解决了这个问题,将创建对象和初始化设计成一个原则操作
静态内部类实现单例

利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。枚举。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证(本质还是类加载时创建)。

public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private IdGenerator() {}
  // 获取实例静态方法
  public static IdGenerator getInstance() {
  	// 第一次执行时 JVM 会加载SingletonHolder类
    return SingletonHolder.instance;
  }
 
   // 静态内部类。
  private static class SingletonHolder{
  	// 静态成员,SingletonHolder 类加载时创建 IdGenerator 对象实例
    private static final IdGenerator instance = new IdGenerator();
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}
枚举实现单例

最简单的实现方式,基于枚举类型的本身的特性实现。简单安全

public enum IdGenerator {
  INSTANCE;
  private AtomicLong id = new AtomicLong(0);
 
  public long getId() { 
    return id.incrementAndGet();
  }
}

IdGenerator.INSTANCE.getId(); // 获得id

枚举的构造方法私有,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在加载枚举类 IdGenerator 时,

单例模式存在的问题

  • 单例对 OOP 特性的支持不友好
  • 单例会隐藏类之间的依赖关系
  • 单例对代码的扩展性不友好
  • 单例对代码的可测试性不友好
  • 单例不支持有参数的构造函数 (可以利用配置类、配置文件)

如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。

单例类的替代方案:通过工厂模式、IOC 容器保证;通过开发者编码保证…

单例的范围

  1. 进程内单例

    同一个进程中只有该类的一个对象

  2. 线程内单例

    实现:利用 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。Java 语言本身提供了 ThreadLocal 并发工具类,可以更加轻松地实现线程唯一单例。

  3. 集群环境下的单例,跨进程单例

    实现:单例对象序列化并存储到外部共享存储区。进程使用对象时需要读取并反序列化成对象,用完后再将对象序列化存回共享存储区。为了保证任何时刻在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,需要显式地将对象从内存中删除,并且释放对对象的加锁。

多例模式

一个类可以创建多个对象,但是个数是有限制的。同一类型的只能创建一个对象,不同类型的可以创建多个对象。(这里的类型是一个广义含义,比如按照名称分类,不同名称是不同的类型)

public class Logger {
  private static final ConcurrentHashMap<String, Logger> instances
          = new ConcurrentHashMap<>();

  private Logger() {}

  public static Logger getInstance(String loggerName) {
    instances.putIfAbsent(loggerName, new Logger());
    return instances.get(loggerName);
  }

  public void log() {
    //...
  }
}

//l1==l2, l1!=l3
Logger l1 = Logger.getInstance("User.class");
Logger l2 = Logger.getInstance("User.class");
Logger l3 = Logger.getInstance("Order.class");

利用枚举实现多例模式更简单。

多例模式类似工厂模式,区别在于:多例模式创建的时同一个类的对象,工厂模式一般创建的时不同子类的对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值