单例模式
什么是单例模式?
单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。简单来说,就是保证某个类在整个程序中只被创建一次,就像现实生活中的"唯一"概念一样。
为什么需要单例模式?
想象一下这些场景:
- 数据库连接:我们只需要一个数据库连接池,多个连接会浪费资源
- 配置管理器:程序的配置信息只需要加载一次
- 日志记录器:整个程序使用同一个日志记录器
- 缓存管理器:统一管理内存缓存
单例模式的特点
- 私有构造函数:防止外部直接创建实例
- 私有静态实例:保存唯一的实例
- 公共静态访问方法:提供全局访问点
示例 1:饿汉式单例(线程安全)
public class DatabaseConnection {
// 私有静态实例,在类加载时就创建
private static final DatabaseConnection instance = new DatabaseConnection();
// 私有构造函数,防止外部创建实例
private DatabaseConnection() {
System.out.println("数据库连接已创建");
}
// 公共静态方法,提供全局访问点
public static DatabaseConnection getInstance() {
return instance;
}
public void connect() {
System.out.println("连接到数据库...");
}
public void disconnect() {
System.out.println("断开数据库连接...");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 获取数据库连接实例
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
// 验证是否是同一个实例
System.out.println("db1 和 db2 是同一个实例吗?" + (db1 == db2));
db1.connect();
db2.disconnect();
}
}
输出结果:
数据库连接已创建
db1 和 db2 是同一个实例吗?true
连接到数据库...
断开数据库连接...
示例 2:懒汉式单例(线程安全)
public class Logger {
// 私有静态实例,初始为null
private static volatile Logger instance;
// 私有构造函数
private Logger() {
System.out.println("日志记录器已初始化");
}
// 双重检查锁定,确保线程安全
public static Logger getInstance() {
if (instance == null) {
synchronized (Logger.class) {
if (instance == null) {
instance = new Logger();
}
}
}
return instance;
}
public void log(String message) {
System.out.println("[LOG] " + message);
}
public void error(String message) {
System.out.println("[ERROR] " + message);
}
}
// 使用示例
public class Application {
public static void main(String[] args) {
// 第一次调用时才创建实例
Logger logger1 = Logger.getInstance();
Logger logger2 = Logger.getInstance();
System.out.println("logger1 和 logger2 是同一个实例吗?" + (logger1 == logger2));
logger1.log("应用启动");
logger2.error("发生错误");
}
}
输出结果:
日志记录器已初始化
logger1 和 logger2 是同一个实例吗?true
[LOG] 应用启动
[ERROR] 发生错误
示例 3:枚举单例(推荐方式)
public enum ConfigManager {
INSTANCE;
private String databaseUrl;
private String username;
private String password;
ConfigManager() {
System.out.println("配置管理器已加载");
// 从配置文件加载配置
loadConfig();
}
private void loadConfig() {
this.databaseUrl = "jdbc:mysql://localhost:3306/mydb";
this.username = "admin";
this.password = "123456";
}
public String getDatabaseUrl() {
return databaseUrl;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void updateConfig(String databaseUrl, String username, String password) {
this.databaseUrl = databaseUrl;
this.username = username;
this.password = password;
System.out.println("配置已更新");
}
}
// 使用示例
public class ConfigTest {
public static void main(String[] args) {
ConfigManager config1 = ConfigManager.INSTANCE;
ConfigManager config2 = ConfigManager.INSTANCE;
System.out.println("config1 和 config2 是同一个实例吗?" + (config1 == config2));
System.out.println("数据库URL: " + config1.getDatabaseUrl());
System.out.println("用户名: " + config1.getUsername());
// 更新配置
config2.updateConfig("jdbc:mysql://localhost:3306/newdb", "newuser", "newpass");
// 验证配置已更新
System.out.println("更新后的数据库URL: " + config1.getDatabaseUrl());
}
}
输出结果:
配置管理器已加载
config1 和 config2 是同一个实例吗?true
数据库URL: jdbc:mysql://localhost:3306/mydb
用户名: admin
配置已更新
更新后的数据库URL: jdbc:mysql://localhost:3306/newdb
单例模式的优缺点
优点:
- 节省资源:避免重复创建对象,节省内存和 CPU 资源
- 全局访问:提供全局访问点,方便管理
- 保证唯一性:确保某个类只有一个实例
缺点:
- 违反单一职责原则:单例类既要管理自己的实例,又要提供业务功能
- 难以测试:全局状态使得单元测试变得困难
- 可能产生隐藏依赖:其他类可能过度依赖单例
使用场景
- 配置管理:应用程序的配置信息
- 数据库连接池:管理数据库连接
- 日志记录器:统一的日志记录
- 缓存管理器:内存缓存管理
- 线程池:管理线程资源
总结
单例模式是 Java 中最常用的设计模式之一,它确保一个类只有一个实例,并提供全局访问点。通过不同的实现方式(饿汉式、懒汉式、枚举),我们可以根据具体需求选择最合适的方案。在实际开发中,要谨慎使用单例模式,避免过度设计。
