Java单例模式实现与最佳实践
Java单例模式实现与最佳实践
单例模式是Java设计模式中最常用但也最容易用错的一种,本文将深入探讨各种实现方式、原理及其在真实场景下的应用。
什么是单例模式?
单例模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。这在需要控制资源访问、配置管理、线程池等场景中非常有用。
基础实现方式
1. 饿汉式(Eager Initialization)
public class EagerSingleton {
// 类加载时就创建实例
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造方法防止外部实例化
private EagerSingleton() {
// 防止反射攻击
if (INSTANCE != null) {
throw new RuntimeException("单例对象已被创建!");
}
}
// 全局访问点
public static EagerSingleton getInstance() {
return INSTANCE;
}
// 业务方法示例
public void doSomething() {
System.out.println("饿汉式单例方法执行");
}
}优点:
- 实现简单,线程安全
- 无锁性能高
缺点:
- 类加载时就创建,可能造成资源浪费
- 不能延迟加载
2. 懒汉式(Lazy Initialization)
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
// 防止反射攻击
if (instance != null) {
throw new RuntimeException("单例对象已被创建!");
}
}
// 基础懒加载 - 线程不安全!
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}线程安全的单例实现
3. 同步方法懒汉式
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
// 线程安全但性能差
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}缺点: 每次获取实例都需要同步,性能瓶颈
4. 双重检查锁(Double-Checked Locking)
public class DoubleCheckedSingleton {
// 使用volatile防止指令重排序
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {
if (instance != null) {
throw new RuntimeException("单例对象已被创建!");
}
}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) { // 第二次检查
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}关键点:
volatile关键字防止指令重排序- 两次null检查确保线程安全
- 只在第一次创建时同步,性能较好
推荐的实现方式
5. 静态内部类(Static Inner Class)
public class InnerClassSingleton {
private InnerClassSingleton() {
// 防止反射攻击
if (SingletonHolder.INSTANCE != null) {
throw new RuntimeException("单例对象已被创建!");
}
}
// 静态内部类
private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
// 防止反序列化破坏单例
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}优点:
- 懒加载,只有调用getInstance()时才加载内部类
- 线程安全,由JVM类加载机制保证
- 无锁性能高
6. 枚举方式(Enum - 最佳实践)
public enum EnumSingleton {
INSTANCE;
// 添加业务方法
public void doSomething() {
System.out.println("枚举单例方法执行");
}
// 可以持有状态
private String config;
public String getConfig() {
return config;
}
public void setConfig(String config) {
this.config = config;
}
}
// 使用方式
EnumSingleton.INSTANCE.doSomething();
EnumSingleton.INSTANCE.setConfig("app config");枚举单例的优势:
- 绝对防止多实例创建
- 天生线程安全
- 自动防止反射攻击
- 自动防止反序列化重新创建对象
- 代码简洁
防止单例被破坏的完整方案
防御反射、序列化攻击
import java.io.Serializable;
public class PerfectSingleton implements Serializable {
private static volatile PerfectSingleton instance;
private PerfectSingleton() {
// 防御反射攻击
if (instance != null) {
throw new RuntimeException("禁止通过反射创建单例!");
}
}
public static PerfectSingleton getInstance() {
if (instance == null) {
synchronized (PerfectSingleton.class) {
if (instance == null) {
instance = new PerfectSingleton();
}
}
}
return instance;
}
// 防御反序列化攻击
private Object readResolve() {
return instance;
}
// 防御克隆攻击
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("单例对象不支持克隆");
}
}真实场景应用案例
1. 配置管理器
public enum ConfigManager {
INSTANCE;
private Properties config;
ConfigManager() {
loadConfig();
}
private void loadConfig() {
config = new Properties();
try (InputStream input = getClass().getClassLoader()
.getResourceAsStream("application.properties")) {
if (input != null) {
config.load(input);
}
} catch (IOException e) {
throw new RuntimeException("加载配置文件失败", e);
}
}
public String getProperty(String key) {
return config.getProperty(key);
}
public String getProperty(String key, String defaultValue) {
return config.getProperty(key, defaultValue);
}
}
// 使用
String dbUrl = ConfigManager.INSTANCE.getProperty("database.url");2. 数据库连接池
public class DatabaseConnectionPool {
private static final int MAX_POOL_SIZE = 10;
private final List<Connection> connectionPool;
private static class SingletonHolder {
private static final DatabaseConnectionPool INSTANCE =
new DatabaseConnectionPool();
}
private DatabaseConnectionPool() {
connectionPool = new ArrayList<>(MAX_POOL_SIZE);
initializePool();
}
public static DatabaseConnectionPool getInstance() {
return SingletonHolder.INSTANCE;
}
private void initializePool() {
for (int i = 0; i < MAX_POOL_SIZE; i++) {
// 创建数据库连接
// connectionPool.add(createConnection());
}
}
public Connection getConnection() {
// 从连接池获取连接逻辑
return null; // 实际实现中返回真实连接
}
public void releaseConnection(Connection connection) {
// 释放连接回连接池逻辑
}
}3. 日志管理器
public class Logger {
private static volatile Logger instance;
private final List<String> logs;
private Logger() {
this.logs = new ArrayList<>();
}
public static Logger getInstance() {
if (instance == null) {
synchronized (Logger.class) {
if (instance == null) {
instance = new Logger();
}
}
}
return instance;
}
public void log(String message) {
String logEntry = String.format("[%s] %s",
LocalDateTime.now(), message);
logs.add(logEntry);
System.out.println(logEntry);
}
public List<String> getLogs() {
return new ArrayList<>(logs);
}
}单例模式的测试
单元测试示例
class SingletonTest {
@Test
void testSingletonInstance() {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
assertSame(instance1, instance2);
}
@Test
void testThreadSafety() throws InterruptedException {
final int threadCount = 100;
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch endLatch = new CountDownLatch(threadCount);
final Set<Singleton> instances = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
startLatch.await();
instances.add(Singleton.getInstance());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
endLatch.countDown();
}
}).start();
}
startLatch.countDown();
endLatch.await(5, TimeUnit.SECONDS);
assertEquals(1, instances.size());
}
}性能对比与选择建议
| 实现方式 | 线程安全 | 懒加载 | 性能 | 防反射 | 防序列化 | 推荐度 |
|---|---|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ⭐⭐⭐⭐ | ❌ | ❌ | ★★★☆ |
| 同步方法 | ✅ | ✅ | ⭐⭐ | ❌ | ❌ | ★★☆☆ |
| 双重检查锁 | ✅ | ✅ | ⭐⭐⭐ | ✅ | ❌ | ★★★☆ |
| 静态内部类 | ✅ | ✅ | ⭐⭐⭐⭐ | ✅ | ✅ | ★★★★☆ |
| 枚举 | ✅ | ❌ | ⭐⭐⭐⭐ | ✅ | ✅ | ★★★★★ |
最佳实践总结
- 首选枚举方式:简洁、安全、功能完整
- 需要懒加载时选择静态内部类:性能好,实现优雅
- 避免基础懒汉式:线程不安全
- 谨慎使用双重检查锁:实现复杂,容易出错
- 始终考虑序列化和反射攻击
- 在依赖注入框架中慎用单例:Spring等框架有自己的单例管理
常见陷阱与注意事项
- 集群环境下单例不单:每个JVM都会有自己的单例实例
- 类加载器问题:不同的类加载器会创建不同的单例
- 内存泄漏风险:单例对象长期持有可能导致内存无法释放
- 测试困难:单例的全局状态可能影响单元测试的独立性
单例模式虽然简单,但实现一个真正安全、高效的单例需要仔细考虑各种边界情况。根据具体需求选择合适的实现方式,才能写出既安全又高效的代码。