单例模式
单例模式确保一个类只有一个实例,并提供全局访问点。这是最简单的设计模式之一,也是最常用的模式之一。
📋 模式概述
定义: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用场景:
- 需要频繁创建和销毁的对象
- 创建对象时耗时过多或耗资源过多
- 工具类对象
- 数据库连接池
- 线程池
- 配置管理器
- 日志记录器
🎯 一、项目案例:数据库连接池管理
1.1 问题场景
在电商系统中,我们需要管理数据库连接池。如果每次创建新的连接池,会导致:
- 资源浪费
- 连接数过多
- 管理复杂
1.2 单例模式解决方案
项目背景: 电商系统数据库连接池管理器
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 数据库连接池管理器 - 单例模式实现
* 项目案例:电商系统数据库连接管理
*/
public class DatabaseConnectionPool {
// 私有静态实例
private static volatile DatabaseConnectionPool instance;
// 连接池配置
private static final String DB_URL = "jdbc:mysql://localhost:3306/ecommerce";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
private static final int POOL_SIZE = 10;
// 连接池队列
private BlockingQueue<Connection> connectionPool;
private int currentConnections = 0;
// 私有构造函数,防止外部实例化
private DatabaseConnectionPool() {
initializePool();
}
/**
* 双重检查锁定实现线程安全的单例
*/
public static DatabaseConnectionPool getInstance() {
if (instance == null) {
synchronized (DatabaseConnectionPool.class) {
if (instance == null) {
instance = new DatabaseConnectionPool();
}
}
}
return instance;
}
/**
* 初始化连接池
*/
private void initializePool() {
connectionPool = new LinkedBlockingQueue<>(POOL_SIZE);
try {
// 预创建连接
for (int i = 0; i < POOL_SIZE; i++) {
Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
connectionPool.offer(conn);
currentConnections++;
}
System.out.println("数据库连接池初始化完成,连接数: " + currentConnections);
} catch (SQLException e) {
throw new RuntimeException("初始化数据库连接池失败", e);
}
}
/**
* 获取连接
*/
public Connection getConnection() throws InterruptedException {
Connection conn = connectionPool.take();
if (conn == null || conn.isClosed()) {
// 连接已关闭,创建新连接
conn = createNewConnection();
}
return conn;
}
/**
* 归还连接
*/
public void releaseConnection(Connection conn) {
if (conn != null && !connectionPool.offer(conn)) {
// 连接池已满,关闭连接
try {
conn.close();
currentConnections--;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 创建新连接
*/
private Connection createNewConnection() {
try {
return DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
} catch (SQLException e) {
throw new RuntimeException("创建数据库连接失败", e);
}
}
/**
* 关闭连接池
*/
public void shutdown() {
while (!connectionPool.isEmpty()) {
try {
Connection conn = connectionPool.poll();
if (conn != null && !conn.isClosed()) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
System.out.println("数据库连接池已关闭");
}
}1.3 使用示例
java
/**
* 订单服务 - 使用单例连接池
*/
public class OrderService {
private DatabaseConnectionPool connectionPool;
public OrderService() {
// 获取单例连接池实例
this.connectionPool = DatabaseConnectionPool.getInstance();
}
/**
* 创建订单
*/
public void createOrder(Order order) {
Connection conn = null;
try {
// 从连接池获取连接
conn = connectionPool.getConnection();
// 执行数据库操作
String sql = "INSERT INTO orders (order_id, user_id, amount, status) VALUES (?, ?, ?, ?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, order.getOrderId());
ps.setLong(2, order.getUserId());
ps.setBigDecimal(3, order.getAmount());
ps.setString(4, order.getStatus());
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 归还连接
if (conn != null) {
connectionPool.releaseConnection(conn);
}
}
}
}好处:
- 资源节省: 整个系统只有一个连接池实例,避免重复创建
- 统一管理: 所有数据库操作使用同一个连接池,便于监控和管理
- 线程安全: 使用双重检查锁定确保多线程环境下的安全性
🎯 二、项目案例:配置管理器
2.1 问题场景
在微服务系统中,我们需要统一管理配置信息。如果每个服务都创建自己的配置管理器,会导致:
- 配置分散
- 难以统一修改
- 内存浪费
2.2 单例模式解决方案
java
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 配置管理器 - 单例模式实现
* 项目案例:微服务系统配置管理
*/
public class ConfigManager {
private static ConfigManager instance;
private Properties properties;
// 私有构造函数
private ConfigManager() {
loadConfig();
}
/**
* 懒汉式单例 - 线程安全版本
*/
public static synchronized ConfigManager getInstance() {
if (instance == null) {
instance = new ConfigManager();
}
return instance;
}
/**
* 加载配置文件
*/
private void loadConfig() {
properties = new Properties();
try (InputStream is = getClass().getClassLoader()
.getResourceAsStream("application.properties")) {
if (is != null) {
properties.load(is);
System.out.println("配置文件加载成功");
}
} catch (IOException e) {
throw new RuntimeException("加载配置文件失败", e);
}
}
/**
* 获取配置值
*/
public String getProperty(String key) {
return properties.getProperty(key);
}
/**
* 获取配置值(带默认值)
*/
public String getProperty(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
/**
* 获取整数配置
*/
public int getIntProperty(String key, int defaultValue) {
String value = properties.getProperty(key);
if (value != null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
return defaultValue;
}
/**
* 获取布尔配置
*/
public boolean getBooleanProperty(String key, boolean defaultValue) {
String value = properties.getProperty(key);
if (value != null) {
return Boolean.parseBoolean(value);
}
return defaultValue;
}
}2.3 使用示例
java
/**
* 用户服务 - 使用单例配置管理器
*/
public class UserService {
private ConfigManager configManager;
public UserService() {
// 获取单例配置管理器
this.configManager = ConfigManager.getInstance();
}
/**
* 发送邮件验证码
*/
public void sendVerificationEmail(String email) {
// 从配置管理器获取邮件服务器配置
String smtpHost = configManager.getProperty("email.smtp.host");
int smtpPort = configManager.getIntProperty("email.smtp.port", 587);
String username = configManager.getProperty("email.username");
String password = configManager.getProperty("email.password");
// 发送邮件逻辑
EmailSender sender = new EmailSender(smtpHost, smtpPort, username, password);
sender.send(email, "验证码", generateCode());
}
}好处:
- 统一配置: 所有服务使用同一个配置管理器,配置集中管理
- 内存节省: 只加载一次配置文件,避免重复加载
- 易于维护: 配置修改只需在一个地方进行
🎯 三、单例模式的多种实现方式
3.1 饿汉式(线程安全)
java
/**
* 饿汉式单例 - 线程安全
* 优点:线程安全,实现简单
* 缺点:类加载时就创建实例,可能浪费内存
*/
public class EagerSingleton {
// 类加载时创建实例
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}3.2 懒汉式(线程不安全)
java
/**
* 懒汉式单例 - 线程不安全
* 优点:延迟加载,节省内存
* 缺点:多线程环境下不安全
*/
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton(); // 多线程可能创建多个实例
}
return instance;
}
}3.3 懒汉式(线程安全)
java
/**
* 懒汉式单例 - synchronized版本(线程安全)
* 优点:线程安全
* 缺点:每次获取实例都要加锁,性能较差
*/
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}3.4 双重检查锁定(推荐)
java
/**
* 双重检查锁定单例 - 线程安全且性能好
* 优点:线程安全,延迟加载,性能好
* 缺点:实现相对复杂
*/
public class DoubleCheckSingleton {
// 使用volatile确保可见性
private static volatile DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}3.5 静态内部类(推荐)
java
/**
* 静态内部类单例 - 线程安全且延迟加载
* 优点:线程安全,延迟加载,实现简单,推荐使用
*/
public class InnerClassSingleton {
private InnerClassSingleton() {}
/**
* 静态内部类,只有在被调用时才会加载
*/
private static class SingletonHolder {
private static final InnerClassSingleton instance = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.instance;
}
}3.6 枚举单例(推荐)
java
/**
* 枚举单例 - 线程安全,防止反序列化和反射攻击
* 优点:线程安全,实现简单,防止反序列化和反射攻击
* 缺点:不能延迟加载
*/
public enum EnumSingleton {
INSTANCE;
// 可以添加方法
public void doSomething() {
System.out.println("枚举单例执行操作");
}
}
// 使用方式
EnumSingleton.INSTANCE.doSomething();🎯 四、Spring中的单例
4.1 Spring单例模式
Spring容器默认就是单例模式:
java
@Service
public class UserService {
// Spring容器中默认就是单例
}
@Controller
public class UserController {
@Autowired
private UserService userService; // 注入的是同一个实例
}4.2 自定义单例Bean
java
@Component
public class CacheManager {
private 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);
}
}📖 最佳实践
1. 选择合适的实现方式
- 需要延迟加载: 使用静态内部类或双重检查锁定
- 不需要延迟加载: 使用饿汉式或枚举
- 需要防止反射和反序列化: 使用枚举
2. 注意线程安全
- 多线程环境必须使用线程安全的实现
- 推荐使用静态内部类或枚举方式
3. 避免过度使用
- 不是所有需要唯一实例的场景都适合单例
- 单例模式会增加代码的耦合度
✅ 学习检查清单
- [ ] 已理解单例模式的定义和适用场景
- [ ] 已掌握各种单例实现方式
- [ ] 已理解线程安全的重要性
- [ ] 已在项目中应用单例模式
- [ ] 已了解单例模式的优缺点