单例模式
用于创建那些在软件系统中独一无二的对象。
在实际的开发过程中,为了节约系统的资源,有时需要确保系统中某个类的实例必须是唯一的,比如:线程池、缓存、网络请求等所有的操作都需要基于唯一的实例,我们就可以使用
单例模式
。
1. 定义
单例模式
确保了一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例,这个类被称为单例类
。它提供全局访问的方法。
2. 角色
- Singleton:
单例类
的内部实现只生成一个示例,同时提供一个静态的方法,向系统提供全局访问的方法。为了防止在外部对单例类实例化,将构造方法设置为private
。
3. 示例
单例的实现共有以下几种方式:
3.1 饿汉式
代码语言:javascript复制饿汉式的单例实现比较简单,其在类加载的时候,静态实例
instance
就已创建并初始化好了。
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 双重检查
代码语言:javascript复制双重检查将懒汉式中的方法锁改为块锁,再次调用时不会进入
synchronized
代码块中。
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 静态内部类
代码语言:javascript复制使用Java静态内部类的特性:Java 加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例。
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 枚举
代码语言:javascript复制用枚举来实现单例,是最简单的方式。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。
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
代码语言:javascript复制CAS使用乐观锁的机制,通过自旋不断尝试更新值,实现了线程安全。
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 组件,如窗口管理器、对话框管理器等,可以使用单例模式确保全局唯一性。
- 计数器: 在一些需要生成唯一序列号或计数的情况下,可以使用单例模式来管理计数器,确保唯一性和一致性。
- 系统状态管理: 在某些系统中,可能需要维护和管理系统的状态信息,例如登录状态、权限信息等,单例模式可以确保状态信息的一致性和全局访问。
我会持续更新关于技术的文章❤️