Java单例模式的用处详解
单例模式是Java中最常用的设计模式之一,它确保一个类只有一个实例,并提供一个全局访问点。下面详细解析单例模式的用处、实现方式及应用场景。
一、单例模式的核心用处
1. 资源管理与共享
数据库连接池:避免频繁创建和销毁数据库连接
线程池:统一管理线程资源
缓存系统:全局共享缓存数据
配置文件管理:统一读取和管理配置信息
2. 控制资源访问
日志记录器:确保所有日志写入同一个文件
打印池:控制打印任务的顺序执行
设备驱动:如打印机、显卡等硬件设备控制
3. 性能优化
减少对象创建开销:特别是重量级对象的创建成本
内存优化:避免重复实例化占用过多内存
4. 状态一致性
计数器:全局计数保持一致性
序列号生成器:确保ID唯一且有序
应用状态管理:如购物车、用户会话等
二、单例模式的实现方式
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() {}
// 线程不安全版本
public static LazySingleton getInstanceUnsafe() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
// 线程安全版本 - 方法同步
public static synchronized LazySingleton getInstanceSync() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
优点:延迟加载,节省资源
缺点:同步方法性能较低
3. 双重检查锁定(Double-Checked Locking)
public class DoubleCheckedSingleton {
// 使用volatile保证可见性和禁止指令重排序
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
public static DoubleCheckedSingleton getInstance() {
// 第一次检查,避免不必要的同步
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
// 第二次检查,确保只有一个线程创建实例
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
优点:线程安全且性能较好
缺点:实现稍复杂,需要理解内存模型
4. 静态内部类(Static Inner Class)
public class InnerClassSingleton {
private InnerClassSingleton() {}
// 静态内部类在第一次被引用时才会加载
private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点:线程安全,延迟加载,实现简单
推荐:这是最优雅的实现方式之一
5. 枚举单例(Enum Singleton) – 推荐方式
public enum EnumSingleton {
INSTANCE;
// 业务方法
public void doSomething() {
System.out.println("枚举单例方法执行");
}
// 可以添加属性
private String data = "单例数据";
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
// 使用示例
EnumSingleton.INSTANCE.doSomething();
优点:
绝对防止多次实例化
自动处理序列化问题
线程安全
防止反射攻击
三、实际应用场景示例
1. 数据库连接池单例
public class DatabaseConnectionPool {
private static volatile DatabaseConnectionPool instance;
private final Connection[] connections;
private final boolean[] used;
private DatabaseConnectionPool() {
// 初始化连接池
connections = new Connection[10];
used = new boolean[10];
initializeConnections();
}
public static DatabaseConnectionPool getInstance() {
if (instance == null) {
synchronized (DatabaseConnectionPool.class) {
if (instance == null) {
instance = new DatabaseConnectionPool();
}
}
}
return instance;
}
private void initializeConnections() {
try {
for (int i = 0; i < connections.length; i++) {
connections[i] = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "user", "password");
used[i] = false;
}
} catch (SQLException e) {
throw new RuntimeException("数据库连接初始化失败", e);
}
}
public synchronized Connection getConnection() {
for (int i = 0; i < used.length; i++) {
if (!used[i]) {
used[i] = true;
return connections[i];
}
}
throw new RuntimeException("连接池已满");
}
public synchronized void releaseConnection(Connection conn) {
for (int i = 0; i < connections.length; i++) {
if (connections[i] == conn) {
used[i] = false;
break;
}
}
}
}
2. 配置管理器单例
public class ConfigManager {
private static final ConfigManager INSTANCE = new ConfigManager();
private final Properties properties;
private ConfigManager() {
properties = new Properties();
loadConfig();
}
public static ConfigManager getInstance() {
return INSTANCE;
}
private void loadConfig() {
try (InputStream input = getClass().getClassLoader()
.getResourceAsStream("config.properties")) {
if (input == null) {
throw new RuntimeException("配置文件未找到");
}
properties.load(input);
} 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);
}
}
// 使用示例
String dbUrl = ConfigManager.getInstance().getProperty("database.url");
3. 日志记录器单例
public class Logger {
private static final Logger INSTANCE = new Logger();
private PrintWriter writer;
private Logger() {
try {
writer = new PrintWriter(new FileWriter("application.log", true), true);
} catch (IOException e) {
throw new RuntimeException("日志文件初始化失败", e);
}
}
public static Logger getInstance() {
return INSTANCE;
}
public void log(String level, String message) {
String logEntry = String.format("[%s] %s: %s",
LocalDateTime.now(), level.toUpperCase(), message);
writer.println(logEntry);
}
public void info(String message) {
log("INFO", message);
}
public void error(String message) {
log("ERROR", message);
}
public void close() {
if (writer != null) {
writer.close();
}
}
}
4. 缓存管理器单例
public class CacheManager {
private static final CacheManager INSTANCE = new CacheManager();
private final Map<String, Object> cache;
private final ScheduledExecutorService cleaner;
private CacheManager() {
cache = new ConcurrentHashMap<>();
cleaner = Executors.newScheduledThreadPool(1);
// 每隔5分钟清理过期缓存
cleaner.scheduleAtFixedRate(this::cleanExpired, 5, 5, TimeUnit.MINUTES);
}
public static CacheManager getInstance() {
return INSTANCE;
}
public void put(String key, Object value, long ttlMinutes) {
CacheEntry entry = new CacheEntry(value,
System.currentTimeMillis() + ttlMinutes * 60 * 1000);
cache.put(key, entry);
}
public Object get(String key) {
CacheEntry entry = (CacheEntry) cache.get(key);
if (entry != null && !entry.isExpired()) {
return entry.getValue();
}
cache.remove(key);
return null;
}
private void cleanExpired() {
cache.entrySet().removeIf(entry -> {
CacheEntry cacheEntry = (CacheEntry) entry.getValue();
return cacheEntry.isExpired();
});
}
private static class CacheEntry {
private final Object value;
private final long expirationTime;
CacheEntry(Object value, long expirationTime) {
this.value = value;
this.expirationTime = expirationTime;
}
public Object getValue() { return value; }
public boolean isExpired() { return System.currentTimeMillis() > expirationTime; }
}
}
四、单例模式的注意事项
1. 序列化问题
public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton() {}
public static SerializableSingleton getInstance() {
return INSTANCE;
}
// 防止反序列化创建新实例
protected Object readResolve() {
return INSTANCE;
}
}
2. 反射攻击防护
public class ReflectionSafeSingleton {
private static final ReflectionSafeSingleton INSTANCE = new ReflectionSafeSingleton();
private ReflectionSafeSingleton() {
// 防止反射创建实例
if (INSTANCE != null) {
throw new RuntimeException("单例模式不允许通过反射创建实例");
}
}
public static ReflectionSafeSingleton getInstance() {
return INSTANCE;
}
}
3. 克隆防护
public class CloneSafeSingleton implements Cloneable {
private static final CloneSafeSingleton INSTANCE = new CloneSafeSingleton();
private CloneSafeSingleton() {}
public static CloneSafeSingleton getInstance() {
return INSTANCE;
}
// 防止克隆
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("单例对象不支持克隆");
}
}
五、单例模式的优缺点总结
优点:
严格控制实例数量:确保全局唯一实例
全局访问点:方便其他类访问
节省资源:避免重复创建重量级对象
状态一致性:保证全局状态统一
缺点:
违反单一职责原则:同时负责业务逻辑和实例控制
难以测试:全局状态可能影响单元测试
隐藏的依赖关系:类之间的依赖关系不够明确
可能成为上帝对象:承担过多职责
六、替代方案
在某些场景下,可以考虑以下替代方案:
1. 依赖注入
// 使用Spring框架的依赖注入
@Component
public class DatabaseService {
// 通过依赖注入获得单例
}
2. 工厂模式
public class ObjectFactory {
private static final Map<String, Object> instances = new HashMap<>();
public static synchronized Object getInstance(String key) {
return instances.computeIfAbsent(key, k -> createObject(k));
}
}
总结
单例模式在以下场景中特别有用:
需要严格控制资源访问的场景
需要全局状态管理的场景
重量级对象需要复用的场景
需要保持状态一致性的场景
但也要注意不要滥用单例模式,特别是在需要测试、需要灵活扩展的场景中,应该考虑使用依赖注入等其他设计模式。选择枚举或静态内部类实现方式,可以更好地保证单例的安全性和简洁性。















暂无评论内容