单例模式(Singleton Pattern)
是一种创建型设计模式,保证一个类在整个程序的生命周期中只有一个实例,并提供一个全局访问点。单例模式广泛用于需要全局唯一实例的场景,比如数据库连接池、日志对象、线程池等。
单例模式的特点
- 唯一性:确保一个类只有一个实例。
- 延迟实例化:通常使用懒加载机制,即在第一次使用时才创建实例。
- 全局访问:提供一个全局访问点供外部获取该实例。
单例模式的实现方式
单例模式有多种实现方式,常见的有以下几种:
1. 懒汉式(Lazy Initialization)
懒汉式的单例模式是延迟初始化的,实例在第一次使用时才创建。这种方式的缺点是线程不安全。
public class SingletonLazy {
private static SingletonLazy instance;
// 私有化构造函数,防止外部实例化
private SingletonLazy() {}
// 获取单例对象的静态方法
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
缺点: 懒汉式在多线程环境下可能会创建多个实例,因为线程可能会同时通过 if (instance == null)
条件。
2. 线程安全的懒汉式(Synchronized Method)
为了在多线程环境下保证实例的唯一性,可以使用 synchronized
关键字使方法同步。
public class SingletonLazyThreadSafe {
private static SingletonLazyThreadSafe instance;
private SingletonLazyThreadSafe() {}
// 同步方法保证线程安全
public static synchronized SingletonLazyThreadSafe getInstance() {
if (instance == null) {
instance = new SingletonLazyThreadSafe();
}
return instance;
}
}
缺点: 虽然线程安全,但由于 synchronized
关键字的使用,每次调用 getInstance
都会产生一定的性能开销。
3. 双重检查锁定(Double-Checked Locking)
双重检查锁定在性能和线程安全之间做了折中。通过在实例初始化前后都进行检查,减少同步的开销。
public class SingletonDCL {
// volatile 保证变量的可见性和有序性
private static volatile SingletonDCL instance;
private SingletonDCL() {}
public static SingletonDCL getInstance() {
if (instance == null) { // 第一次检查
synchronized (SingletonDCL.class) {
if (instance == null) { // 第二次检查
instance = new SingletonDCL();
}
}
}
return instance;
}
}
优点: 在多线程环境中高效且线程安全。
4. 静态内部类(Static Inner Class)
利用 Java 的类加载机制实现懒加载,静态内部类方式在类加载时不会立即实例化,而是在调用 getInstance()
时才加载内部类并创建实例。
public class SingletonStaticInnerClass {
private SingletonStaticInnerClass() {}
// 静态内部类持有 Singleton 的唯一实例
private static class SingletonHolder {
private static final SingletonStaticInnerClass INSTANCE = new SingletonStaticInnerClass();
}
public static SingletonStaticInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点: 线程安全,且没有性能开销,推荐使用。
5. 枚举(Enum Singleton)
使用枚举实现单例是一种比较优雅的方式,因为 Java 枚举本身是线程安全的,并且只能有一个实例。
public enum SingletonEnum {
INSTANCE;
public void someMethod() {
// 业务逻辑
}
}
优点: 实现简单,枚举保证了线程安全且防止了反序列化破坏单例。
单例模式使用场景
- 配置类:当一个类只需要一个实例来提供全局配置时,比如读取配置文件的类。
- 数据库连接池:只需要一个连接池对象来管理数据库连接。
- 日志类:应用程序中通常需要一个全局的日志管理类。
- 线程池:应用程序只需要一个线程池来管理线程。
单例模式的使用技巧
-
防止反射破坏单例: 通过私有化构造器的方式可以阻止外部类直接使用
new
创建实例,但通过反射可以访问私有构造器破坏单例。为了解决这个问题,可以在构造器内加一个判断:private Singleton() { if (instance != null) { throw new RuntimeException("单例模式被破坏"); } }
-
防止序列化破坏单例: 如果单例类实现了
Serializable
接口,在反序列化时可能会创建新的实例。为了解决这个问题,可以在类中实现readResolve
方法:private Object readResolve() { return instance; }
-
线程安全优化: 双重检查锁定(DCL)方式在保证线程安全的前提下,提高了性能,是多线程环境下常用的实现方式。
-
枚举单例推荐: 使用枚举实现单例模式可以简化代码,保证线程安全,并且防止反射和序列化破坏单例,因此被认为是最推荐的方式。
单例模式的实际实例
假设我们需要一个日志管理器,该管理器只允许创建一个实例,并能通过全局访问点进行日志记录。
public class LoggerSingleton {
private static LoggerSingleton instance;
private LoggerSingleton() {}
// 双重检查锁定获取单例实例
public static LoggerSingleton getInstance() {
if (instance == null) {
synchronized (LoggerSingleton.class) {
if (instance == null) {
instance = new LoggerSingleton();
}
}
}
return instance;
}
public void log(String message) {
System.out.println("Log: " + message);
}
}
public class Main {
public static void main(String[] args) {
LoggerSingleton logger = LoggerSingleton.getInstance();
logger.log("This is a singleton logger.");
}
}
总结
- 单例模式提供了确保类实例唯一的有效方案,适用于全局共享资源。
- 常见实现方式包括懒汉式、双重检查锁定和静态内部类等。
- 推荐使用枚举实现单例,因为它天然防止了反射和序列化破坏单例的风险。