Java单例模式
Java单例模式[编辑 | 编辑源代码]
单例模式(Singleton Pattern)是一种常用的软件设计模式,属于创建型模式的一种。它确保一个类只有一个实例,并提供一个全局访问点。这种模式通常用于控制资源访问,如配置管理、线程池、缓存、日志对象等。
介绍[编辑 | 编辑源代码]
单例模式的核心思想是限制类的实例化次数,确保在整个应用程序中只有一个实例存在。这样做的好处包括:
- 资源节约:避免重复创建对象,减少内存开销。
- 全局访问:提供一个统一的访问点,便于管理和维护。
- 状态共享:所有使用者共享同一个实例的状态。
单例模式的关键实现要点: 1. 私有化构造函数,防止外部直接实例化。 2. 提供一个静态方法获取唯一实例。 3. 在多线程环境下保证线程安全。
实现方式[编辑 | 编辑源代码]
1. 饿汉式(Eager Initialization)[编辑 | 编辑源代码]
最简单的实现方式,在类加载时就创建实例。
public class EagerSingleton {
// 类加载时就初始化
private static final EagerSingleton instance = new EagerSingleton();
// 私有构造函数
private EagerSingleton() {}
// 全局访问点
public static EagerSingleton getInstance() {
return instance;
}
}
优点:实现简单,线程安全
缺点:即使不使用也会创建实例,可能造成资源浪费
2. 懒汉式(Lazy Initialization)[编辑 | 编辑源代码]
延迟实例化,在第一次使用时才创建实例。
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
优点:按需创建,节省资源
缺点:非线程安全,多线程环境下可能创建多个实例
3. 线程安全的懒汉式[编辑 | 编辑源代码]
通过同步方法保证线程安全。
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}
优点:线程安全
缺点:每次获取实例都需要同步,性能较差
4. 双重检查锁定(Double-Checked Locking)[编辑 | 编辑源代码]
优化后的线程安全实现,减少同步开销。
public class DCLSingleton {
private volatile static DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}
注意:必须使用volatile关键字防止指令重排序
5. 静态内部类实现[编辑 | 编辑源代码]
利用类加载机制保证线程安全,同时实现延迟加载。
public class InnerClassSingleton {
private InnerClassSingleton() {}
private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点:线程安全,延迟加载,实现简单
6. 枚举实现(推荐方式)[编辑 | 编辑源代码]
Joshua Bloch在《Effective Java》中推荐的方式。
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// 实例方法
}
}
优点:线程安全,防止反射攻击,自动支持序列化
类图[编辑 | 编辑源代码]
实际应用案例[编辑 | 编辑源代码]
1. 配置管理器[编辑 | 编辑源代码]
应用程序通常需要一个全局的配置管理器,单例模式非常适合这种场景。
public class ConfigManager {
private static ConfigManager instance;
private Properties configs;
private ConfigManager() {
// 加载配置文件
configs = new Properties();
try (InputStream input = getClass().getResourceAsStream("/config.properties")) {
configs.load(input);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static synchronized ConfigManager getInstance() {
if (instance == null) {
instance = new ConfigManager();
}
return instance;
}
public String getConfig(String key) {
return configs.getProperty(key);
}
}
2. 数据库连接池[编辑 | 编辑源代码]
数据库连接池通常只需要一个实例来管理所有连接。
public class ConnectionPool {
private static ConnectionPool instance;
private List<Connection> pool;
private ConnectionPool() {
// 初始化连接池
pool = new ArrayList<>();
for (int i = 0; i < 10; i++) {
pool.add(createConnection());
}
}
public static synchronized ConnectionPool getInstance() {
if (instance == null) {
instance = new ConnectionPool();
}
return instance;
}
public Connection getConnection() {
// 获取可用连接逻辑
}
public void releaseConnection(Connection conn) {
// 释放连接逻辑
}
private Connection createConnection() {
// 创建新连接
}
}
注意事项[编辑 | 编辑源代码]
1. 线程安全:在多线程环境中必须考虑线程安全问题 2. 序列化:如果需要序列化单例对象,必须实现readResolve方法防止反序列化创建新实例 3. 反射攻击:可以通过反射调用私有构造函数,需要额外防护 4. 单元测试:单例模式可能使单元测试变得困难,因为状态是共享的
性能考虑[编辑 | 编辑源代码]
不同实现方式的性能差异可以通过以下公式表示:
其中:
- 是获取实例的总时间
- 是访问静态变量的时间
- 是同步开销
- 是空检查时间
- 是实例为null的概率
最佳实践[编辑 | 编辑源代码]
1. 在Java 5+环境中,优先使用枚举实现 2. 如果需要延迟加载,考虑静态内部类实现 3. 避免使用反射破坏单例 4. 在依赖注入框架中,通常不需要手动实现单例
常见问题[编辑 | 编辑源代码]
Q: 单例模式是反模式吗? A: 单例模式有时被认为是一种反模式,因为它引入了全局状态,使代码难以测试。但在管理共享资源等场景下仍然有用。
Q: 如何测试单例类? A: 可以通过以下方式:
- 提供重置实例的方法(仅用于测试)
- 使用依赖注入
- 将单例包装在可测试的接口后面
Q: 单例模式和静态类有什么区别? A: 主要区别:
- 单例可以实现接口,可以继承
- 单例可以延迟初始化
- 单例可以序列化
- 静态类所有方法都必须是静态的