以下内容由AI生成,仅作参考与记录
单例模式(Singleton Pattern)是 Java 中最常用的设计模式之一,属于创建型模式。它的核心目标是确保一个类只有一个实例,并提供该实例的全局访问点。
核心思想
- 私有化构造方法:禁止通过
new
创建实例。 - 静态私有成员变量:保存类的唯一实例。
- 静态公有方法:提供获取唯一实例的入口。
常见实现方式
1. 饿汉式(Eager Initialization)
在类加载时直接创建实例,线程安全但可能浪费资源。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {} // 私有构造
public static Singleton getInstance() {
return instance;
}
}
2. 懒汉式(Lazy Initialization)
延迟实例化,但需处理多线程安全问题。
public class Singleton {
private static Singleton instance;
private Singleton() {}
// 方法加锁(影响性能)
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. 双重检查锁(Double-Checked Locking)
减少同步开销,线程安全且高效。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
4. 静态内部类(Static Inner Class)
利用类加载机制保证线程安全,延迟加载。
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
5. 枚举(Enum,推荐方式)
简洁、线程安全、防止反射攻击,是《Effective Java》推荐的方式。
public enum Singleton {
INSTANCE; // 单例实例
public void doSomething() {
// 业务逻辑
}
}
应用场景
- 需要频繁创建销毁的对象(如线程池、数据库连接池)。
- 全局配置管理类(如读取配置文件)。
- 工具类(无状态的工具方法)。
注意事项
- 线程安全:确保多线程环境下单例的唯一性。
- 反射攻击:通过反射可以破坏单例,需额外处理(如枚举天然防御)。
- 序列化问题:反序列化可能生成新对象,需实现
readResolve()
方法。 - 避免滥用:单例模式会引入全局状态,增加代码耦合度。
为什么推荐枚举实现?
- 天然防止反射和序列化破坏单例。
- 代码简洁,无复杂同步逻辑。
- 由 JVM 保证线程安全。
枚举实现单例模式不仅简洁安全,还能通过丰富的代码结构实现复杂逻辑。以下是不同场景下的具体示例:
示例 1:基础工具类单例
实现一个全局的日期格式化工具类,提供线程安全的日期格式化方法:
public enum DateUtils {
INSTANCE;
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public String format(Date date) {
return sdf.format(date);
}
public Date parse(String dateStr) throws ParseException {
return sdf.parse(dateStr);
}
}
// 使用示例
String dateStr = DateUtils.INSTANCE.format(new Date());
示例 2:带初始化的配置管理单例
单例加载配置文件并缓存配置信息:
public enum ConfigManager {
INSTANCE;
private Properties config;
// 枚举构造方法默认私有,在类加载时执行
ConfigManager() {
try (InputStream is = getClass().getResourceAsStream("/app.properties")) {
config = new Properties();
config.load(is);
} catch (IOException e) {
throw new RuntimeException("Failed to load config", e);
}
}
public String getProperty(String key) {
return config.getProperty(key);
}
}
// 使用示例
String dbUrl = ConfigManager.INSTANCE.getProperty("database.url");
示例 3:实现接口的日志单例
单例实现日志接口,灵活扩展功能:
public interface Logger {
void log(String message);
}
public enum FileLogger implements Logger {
INSTANCE;
private File logFile;
FileLogger() {
logFile = new File("app.log");
}
@Override
public void log(String message) {
try (FileWriter writer = new FileWriter(logFile, true)) {
writer.write(message + "\n");
} catch (IOException e) {
System.err.println("Log failed: " + e.getMessage());
}
}
}
// 使用示例
FileLogger.INSTANCE.log("User logged in at " + new Date());
示例 4:数据库连接池管理
单例管理数据库连接池,确保全局唯一资源:
public enum ConnectionPool {
INSTANCE;
private HikariDataSource dataSource;
ConnectionPool() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
dataSource = new HikariDataSource(config);
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public void shutdown() {
if (dataSource != null) {
dataSource.close();
}
}
}
// 使用示例
Connection conn = ConnectionPool.INSTANCE.getConnection();
示例 5:支持多方法的缓存管理
单例作为全局缓存,支持多种操作:
public enum CacheManager {
INSTANCE;
private final Map<String, Object> cache = new ConcurrentHashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
public void remove(String key) {
cache.remove(key);
}
public void clear() {
cache.clear();
}
}
// 使用示例
CacheManager.INSTANCE.put("user:1001", "Alice");
String user = (String) CacheManager.INSTANCE.get("user:1001");
关键优势总结
- 防反射攻击:枚举实例由 JVM 保证唯一性,无法通过反射创建新实例。
- 线程安全:枚举实例在类加载时初始化,天然线程安全。
- 序列化安全:默认处理序列化和反序列化,无需额外代码。
- 代码简洁:无需手动处理双重检查锁或同步问题。
适用场景
- 需要严格全局唯一性的组件(如配置管理、资源池)。
- 工具类或服务类(如日志、缓存、线程池)。
- 希望避免反射/序列化破坏单例的场景。