探索单例模式的奥秘

2024-02-28 22:45:45 浏览数 (2)

单例模式用于创建那些在软件系统中独一无二的对象。

在实际的开发过程中,为了节约系统的资源,有时需要确保系统中某个类的实例必须是唯一的,比如:线程池、缓存、网络请求等所有的操作都需要基于唯一的实例,我们就可以使用单例模式

1. 定义

单例模式确保了一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例,这个类被称为单例类。它提供全局访问的方法。

2. 角色

  • Singleton单例类的内部实现只生成一个示例,同时提供一个静态的方法,向系统提供全局访问的方法。为了防止在外部对单例类实例化,将构造方法设置为private

3. 示例

单例的实现共有以下几种方式:

3.1 饿汉式

饿汉式的单例实现比较简单,其在类加载的时候,静态实例instance 就已创建并初始化好了。

代码语言:javascript复制
 public class HungrySingleton {
     private static final HungrySingleton instance = new HungrySingleton();
     
     private HungrySingleton() {}
     
     public static HungrySingleton getInstance() {
         return instance;
     }
 }
  • 优点:线程安全
  • 缺点:不支持延迟加载(延迟加载可以节省内存)

3.2 懒汉式

懒汉式将对象的初始化延迟到获取实例的时候,为了保证线程安全添加了锁。

代码语言:javascript复制
 public class LazySingleton {
     private static LazySingleton instance;
 ​
     private LazySingleton() {}
 ​
     public static synchronized LazySingleton getInstance() {
         if (instance == null) {
             instance = new LazySingleton();
         }
         return instance;
     }
 }
  • 优点:支持延时加载。
  • 缺点:获取对象的操作被加上了锁实现线程同步,影响了并发度。

3.3 双重检查

双重检查将懒汉式中的方法锁改为块锁,再次调用时不会进入synchronized代码块中。

代码语言:javascript复制
 public class DoubleSingleton {
     // 1.4及更早版本如果不加volatile关键字会导致指令重排,高版本进行了优化,所以也可以不添加
     private static volatile DoubleSingleton instance;
 ​
     private DoubleSingleton() {}
 ​
     public static DoubleSingleton getInstance() {
         if (instance == null) {
             synchronized (DoubleSingleton.class) {
                 if (instance == null) {
                     instance = new DoubleSingleton();
                 }
             }
         }
         return instance;
     }
 }
  • 优点:支持延迟加载,线程安全,资源利用率高,第一次执行 getInstance 时才会被实例化,效率高。
  • 缺点:一次加载反应稍慢。

3.4 静态内部类

使用Java静态内部类的特性:Java 加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例。

代码语言:javascript复制
 public class InnerClassSingleton {
 ​
     private InnerClassSingleton() {}
 ​
     private static class Singleton{
         private static final Singleton instance = new Singleton();
     }
 ​
     public static Singleton getInstance() {
         return Singleton.instance;
     }
 }
  • 优点:线程安全、延迟加载、不需要获取锁。

3.5 枚举

用枚举来实现单例,是最简单的方式。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

代码语言:javascript复制
 public enum EnumSingleton {
     INSTANCE; // 该对象全局唯一
 }

3.6 注册式单例

注册式单例是通过一个注册表(容器)来存储单例实例,根据键值进行管理。

代码语言:javascript复制
 public class RegistrySingleton {
     private static Map<String, RegistrySingleton> map = new HashMap<>();
 ​
     static {
         RegistrySingleton registrySingleton = new RegistrySingleton();
         map.put(registrySingleton.getClass().getName(), registrySingleton);
     }
 ​
     private RegistrySingleton() {
     }
 ​
     public static RegistrySingleton getInstance(String className) {
         if (!map.containsKey(className)) {
             try {
                 map.put(className, (RegistrySingleton) Class.forName(className).newInstance());
             } catch (InstantiationException e) {
                 e.printStackTrace();
             } catch (IllegalAccessException e) {
                 e.printStackTrace();
             } catch (ClassNotFoundException e) {
                 e.printStackTrace();
             }
         }
         return map.get(className);
     }
 }
  • 优点:延迟加载、注册表可以提供全局访问点,方便管理系统中所有的单例;
  • 缺点:线程不安全、引入了注册表,增加复杂性。

3.7 CAS

CAS使用乐观锁的机制,通过自旋不断尝试更新值,实现了线程安全。

代码语言:javascript复制
 public class CasSingleton {
     private static final AtomicReference<CasSingleton> INSTANCE = new AtomicReference<>();
 ​
     private CasSingleton() {
     }
     
     public static CasSingleton getInstance() {
         while (true) {
             CasSingleton singleton = INSTANCE.get();
             if (singleton != null) {
                 return singleton;
             }
             singleton = new CasSingleton();
             if (INSTANCE.compareAndSet(null, singleton)) {
                 return singleton;
             }
         }
     }
 }
  • 优点:线程安全、性能好、无死锁的风险,使用与高并发场景
  • 缺点:会出现ABA问题,不能保证公平性,硬件和平台依赖性较高

4. 应用场景

例模式适用于需要全局唯一实例、提供全局访问点以及确保一致性的场景。

  • 数据库连接池: 在数据库连接池中,为了节省资源和提高性能,通常需要保证只有一个连接池实例,以便管理和复用数据库连接。
  • 日志系统: 在应用程序中使用单例模式来管理日志输出,确保日志信息的一致性和集中管理。
  • 配置管理: 在配置管理中,单例模式可以用于加载和管理系统配置信息,以确保在整个系统中只有一个配置管理实例。
  • 线程池: 在多线程环境中,使用单例模式管理线程池,确保线程的创建和销毁能够集中控制,以便更好地管理系统资源。
  • 资源管理器: 在图形用户界面(GUI)应用程序中,单例模式可以用于实现资源管理器,确保只有一个资源管理器实例用于管理系统资源。
  • 缓存管理: 在需要缓存数据的场景中,单例模式可以用于管理缓存,确保缓存的一致性和集中管理。
  • GUI组件: 在图形用户界面中,一些全局的 GUI 组件,如窗口管理器、对话框管理器等,可以使用单例模式确保全局唯一性。
  • 计数器: 在一些需要生成唯一序列号或计数的情况下,可以使用单例模式来管理计数器,确保唯一性和一致性。
  • 系统状态管理: 在某些系统中,可能需要维护和管理系统的状态信息,例如登录状态、权限信息等,单例模式可以确保状态信息的一致性和全局访问。

我会持续更新关于技术的文章❤️

0 人点赞