单例模式全解析:5种写法 + 破坏与防护

文章目录


什么是单例模式?

保证一个类在全局只有一个实例,并提供一个全局访问点。

适用场景:配置类、连接池、日志对象等全局唯一的资源。

实现方式

饿汉式

类加载时就创建实例,天然线程安全。

java 复制代码
class HungrySingleton {
      private static final HungrySingleton instance = new HungrySingleton();

      private HungrySingleton() {}

      public static HungrySingleton getInstance() {
          return instance;
      }
  }

优点:简单,线程安全

缺点:不管用不用都会创建对象,可能浪费内存

懒汉式

方式一(线程不安全)

java 复制代码
  class LazySingleton {
      private static LazySingleton instance;

      private LazySingleton() {}

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

延迟创建对象,但多线程下可能创建多个实例,线程不安全。

方式二(同步方法)

java 复制代码
  class SafeThreadLazySingleton {
      private static SafeThreadLazySingleton instance;

      private SafeThreadLazySingleton() {}

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

加锁保证线程安全,但每次获取对象都要经过锁竞争,性能不好。

方式三(双重检查锁 DCL)

java 复制代码
  class DCLLazySingleton {
      private static volatile DCLLazySingleton instance;

      private DCLLazySingleton() {}

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

第一个 if:判断对象是否已经创建,避免每次都进入同步块,提升性能。

第二个 if:高并发下第一个 if 可能同时放进来多个线程(如 A、B、C),它们被锁阻塞。A进入临界区创建了对象后退出,B 或 C 再进来时,第二个 if 告诉它对象已存在,直接返回。

volatile 的作用:防止指令重排序。new 一个对象分三步:① 分配内存 → ② 初始化对象 → ③将引用指向内存地址。JVM 可能将顺序优化为①③②,此时另一个线程拿到的是未初始化完成的对象,产生空指针问题。volatile禁止这种重排序,保证对象完整创建后才对外可见。

枚举

java 复制代码
enum EnumSingleton {
      INSTANCE;

      public void doSomething() {
          // 业务方法
      }
  }
java 复制代码
  // 使用
  EnumSingleton.INSTANCE.doSomething();

简洁优雅,属于饿汉式。是唯一不会被反射和反序列化破坏的单例实现,Josh Bloch 在《Effective Java》中推荐的方式。

除枚举外,其他单例都可以通过以下方式强行创建新实例:

  • 反射:Constructor.setAccessible(true) 绕过私有构造器
  • 反序列化:对象实现 Serializable 后,反序列化会创建新对象

枚举在 JVM 层面做了保护,这两种方式对它均无效。

相关推荐
重生之我是Java开发战士16 天前
【Java SE】多线程(三):单例模式,阻塞队列,线程池与定时器
java·javascript·单例模式
许彰午17 天前
34_Java设计模式之单例模式
java·单例模式·设计模式
罗超驿19 天前
10.Java单例模式全解析:饿汉式与懒汉式实现及线程安全深度剖析
安全·单例模式·javaee
布朗克16819 天前
33 设计模式精讲
java·单例模式·设计模式
雨浓YN19 天前
基于设计模式的Winform软件框架-01Xml\Log\Ini日志(单例模式+生产者消费者模式)
单例模式·设计模式
仙俊红20 天前
Java 单例模式:类里面为什么可以有自己类型的字段?
java·开发语言·单例模式
swordbob20 天前
prototype 注入到 singleton 里,prototype是否还是线程安全的
安全·spring·单例模式·原型模式
谁似人间西林客22 天前
工业大数据实战:看中国智造如何用数据驱动效率革命
大数据·单例模式
张小姐的猫22 天前
【Linux】多线程 —— 线程池 | 单例模式 | 常见锁
linux·运维·服务器·c++·单例模式·设计模式·策略模式
Java面试题总结23 天前
双重检验锁的单例模式在高并发下的可见性问题
单例模式