每日Java面试场景题知识点之-单例模式
一、单例模式概述
单例模式(Singleton Pattern)是Java中最简单也是最常用的设计模式之一。它保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。在Java企业级项目中,单例模式广泛应用于配置管理、数据库连接池、缓存管理等场景。
二、单例模式的多种实现方式
1. 饿汉式单例模式
java
public class EagerSingleton {
// 在类加载时就创建实例,线程安全但可能造成资源浪费
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
特点:
- 线程安全
- 类加载时就创建实例
- 可能造成资源浪费
2. 懒汉式单例模式(线程不安全)
java
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
**问题:**在多线程环境下,可能创建多个实例。
3. 懒汉式单例模式(线程安全)
java
public class ThreadSafeLazySingleton {
private static ThreadSafeLazySingleton instance;
private ThreadSafeLazySingleton() {}
public static synchronized ThreadSafeLazySingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeLazySingleton();
}
return instance;
}
}
特点:
- 线程安全
- 性能较差(每次获取实例都要同步)
4. 双重检查锁定(DCL)
java
public class DCLSingleton {
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}
特点:
- 线程安全
- 性能较好
- 需要使用volatile关键字防止指令重排序
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;
}
}
特点:
- 线程安全
- 延迟加载
- 性能最好
6. 枚举方式
java
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// 业务逻辑
}
}
特点:
- 线程安全
- 防止反射攻击
- 防止序列化破坏
三、企业级项目中的实际应用场景
1. 配置管理器
在大型企业应用中,配置信息通常需要全局共享且只加载一次。
java
public class ConfigManager {
private static volatile ConfigManager instance;
private Properties properties;
private ConfigManager() {
properties = new Properties();
loadConfig();
}
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) {
if (instance == null) {
instance = new ConfigManager();
}
}
}
return instance;
}
private void loadConfig() {
try {
properties.load(ConfigManager.class.getClassLoader().getResourceAsStream("config.properties"));
} catch (IOException e) {
throw new RuntimeException("加载配置文件失败", e);
}
}
public String getProperty(String key) {
return properties.getProperty(key);
}
}
2. 数据库连接池
数据库连接池是单例模式的典型应用,确保全局只有一个连接池实例。
java
public class ConnectionPool {
private static volatile ConnectionPool instance;
private DataSource dataSource;
private ConnectionPool() {
initDataSource();
}
public static ConnectionPool getInstance() {
if (instance == null) {
synchronized (ConnectionPool.class) {
if (instance == null) {
instance = new ConnectionPool();
}
}
}
return instance;
}
private void initDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(20);
dataSource = new HikariDataSource(config);
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
3. 缓存管理器
在电商系统中,缓存管理器通常需要全局唯一实例。
java
public class CacheManager {
private static volatile CacheManager instance;
private Cache<String, Object> cache;
private CacheManager() {
cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
}
public static CacheManager getInstance() {
if (instance == null) {
synchronized (CacheManager.class) {
if (instance == null) {
instance = new CacheManager();
}
}
}
return instance;
}
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.getIfPresent(key);
}
public void remove(String key) {
cache.invalidate(key);
}
}
四、常见问题及解决方案
1. 线程安全问题
**问题:**在多线程环境下,单例模式可能创建多个实例。
解决方案:
- 使用synchronized关键字
- 使用双重检查锁定(DCL)
- 使用静态内部类
- 使用枚举
2. 反射攻击问题
**问题:**通过反射可以破坏单例模式。
解决方案:
java
private Singleton() {
if (instance != null) {
throw new RuntimeException("单例模式不允许反射创建实例");
}
}
3. 序列化破坏问题
**问题:**通过序列化和反序列化可以破坏单例模式。
解决方案:
java
protected Object readResolve() {
return getInstance();
}
4. 性能问题
**问题:**同步方法会影响性能。
解决方案:
- 使用双重检查锁定
- 使用静态内部类
- 使用枚举
五、最佳实践
- 选择合适的实现方式:根据具体场景选择最适合的单例实现方式
- 考虑线程安全:在多线程环境中必须保证线程安全
- 防止反射和序列化破坏:对于重要的单例类,需要防止反射和序列化破坏
- 考虑资源管理:确保单例类能够正确管理资源
- 避免过度使用:单例模式虽然方便,但过度使用会导致代码耦合度高
六、总结
单例模式是Java企业级开发中的重要设计模式,它保证一个类只有一个实例,并提供全局访问点。在实际项目中,我们需要根据具体场景选择合适的实现方式,并注意线程安全、反射攻击、序列化破坏等问题。
掌握单例模式对于Java开发者来说是非常重要的,它不仅能够帮助我们写出更好的代码,还能够在面试中展示我们的设计能力。
感谢读者观看!