单例设计模式(Singleton Design Pattern)

1 概念

一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

2 实现

实现单例时,需要注意:

  • 构造函数需要是private访问权限的,这样才能避免外部通过new创建实例
  • 考虑对象创建时的线程安全问题
  • 考虑获取对象实例(getInstance())时的性能问题
  • 考虑是否支持延迟加载

单例模式包含以下实现方式:

  • 懒汉式(延迟加载、并发度低)
  • 饿汉式(非延迟加载、并发度高)
  • 双重校验(延迟加载、并发度高、手动保证线程安全)
  • 静态内部类(延迟加载、并发度高、无需手动保证线程安全)
  • 枚举类(延迟加载、并发度高、无需手动保证线程安全、最简单的实现方式)

2.1 懒汉式

使用时,再初始化instance实例

优点:

  • 支持延迟加载

缺点:

  • 获取实例时需要加锁,并发度低
public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

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

2.2 饿汉式

类加载时,instance实例就已经创建并初始化了

优点:

  • 获取实例时不需要加锁,并发度高

缺点:

  • 不支持延迟加载,instance实例可能一直都不会被用到,浪费资源
public class HungrySingleton {

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

2.3 双重校验

支持延迟加载,避免每次获取实例都要加锁

优点:

  • 支持延迟加载
  • 获取实例时不需要加锁,并发度高

缺点:

  • 需要手动保证线程安全(synchronized)
public class DoubleCheckSingleton {


//    private volatile static DoubleCheckSingleton instance; // 低版本java
   private static DoubleCheckSingleton instance;// 高版本java

   private DoubleCheckSingleton (){}

   public static DoubleCheckSingleton getInstance() {
       if (instance == null) {
           synchronized (DoubleCheckSingleton.class) {
               if (instance == null) {
                   instance = new DoubleCheckSingleton();
               }
           }
       }
       return instance;
   }

}

备注(关于volatile关键字的使用):

  • 低版本的 Java 因为指令重排序,可能会导致 DoubleCheckSingleton 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了(另一个线程使用时因为实例还没有被初始化完成,从而会抛出异常)。要解决这个问题,需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序才行

  • 高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)

2.4 静态内部类

创建类时,不初始化instance实例;当调用getInstance()方法时,才会去初始化

优点:

  • 支持延迟加载
  • 获取实例时不需要加锁,并发度高
  • 不需要手动保证线程安全,由jvm保证
public class StaticInnerClassSingleton {

    private static StaticInnerClassSingleton instance;

    public static class InstanceHolder {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InstanceHolder.instance;
    }
}

2.5 枚举

最简单的实现方案

优点:

  • 通过 Java 枚举类型本身的特性,保证了线程安全和实例唯一

缺点:

  • JDK1.5及以上版本才加入enum特性
public enum EnumSingleton {
    INSTANCE;
}

3 思考

3.1 单例的使用场景

  • 处理资源访问冲突
    • 日志类(非单例情况下,多线程往同一个文件写日志可能存在互相覆盖的情况)
  • 表示全局唯一类
    • ID生成器
    • 配置类
    • 连接池类

3.2 单例存在的问题

  • 单例对 OOP 特性的支持不友好
  • 单例会隐藏类之间的依赖关系
  • 单例对代码的扩展性不友好
  • 单例对代码的可测试性不友好
  • 单例不支持有参数的构造函数

3.3 有何替代方案

  • 工厂模式
  • IOC 容器
posted @   可以书  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示