编程自学指南:java程序设计开发,Java 单例设计模式
一、课程信息
学习目标
- 理解单例模式的核心目标:全局唯一实例
- 掌握 4 种经典实现方式(饿汉式、懒汉式、双重检查锁、静态内部类)
- 能根据场景选择合适的单例实现,避免线程安全问题
- 了解单例模式的优缺点及常见应用场景
二、课程导入:生活中的唯一性
🌰 场景 1:电脑任务管理器
- 无论打开多少次,任务管理器窗口始终只有一个
- 程序实现:确保
TaskManager
类全局唯一实例
🌰 场景 2:网站计数器
- 所有用户共享一个计数器实例,避免重复计数
三、单例模式核心概念
1. 定义
三大要点:
- 唯一实例:类在系统中只能有一个实例
- 自行创建:实例由类自身负责创建
- 全局访问:提供静态方法获取实例
四、经典实现方式
🔧 方式 1:饿汉式单例(线程安全)
实现原理:类加载时立即创建实例
class HungrySingleton {
// 1. 静态私有实例(类加载时创建)
private static final HungrySingleton instance = new HungrySingleton();
// 2. 私有构造器(禁止外部创建)
private HungrySingleton() {}
// 3. 全局访问方法
public static HungrySingleton getInstance() {
return instance;
}
}
特点:
✅ 线程安全(类加载机制保证)
❌ 可能造成资源浪费(无论是否使用都会创建)
🔧 方式 2:懒汉式单例(线程不安全)
实现原理:首次调用时创建实例
class LazySingleton {
private static LazySingleton instance; // 初始为null
private LazySingleton() {}
// 未加锁的线程不安全版本
public static LazySingleton getInstance() {
if (instance == null) { // 第一次调用时创建
instance = new LazySingleton();
}
return instance;
}
}
问题演示:
多线程环境下可能创建多个实例(通过Thread
模拟并发调用)
🔧 方式 3:懒汉式(线程安全版)
改进 1:同步方法
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
缺点:每次调用都加锁,性能较低
改进 2:双重检查锁(DCL)
class SafeLazySingleton {
private static volatile SafeLazySingleton instance; // volatile保证可见性
public static SafeLazySingleton getInstance() {
if (instance == null) { // 第一重检查
synchronized (SafeLazySingleton.class) { // 加锁
if (instance == null) { // 第二重检查
instance = new SafeLazySingleton();
}
}
}
return instance;
}
}
🔧 方式 4:静态内部类(推荐)
实现原理:利用类加载机制延迟加载
class StaticInnerSingleton {
private StaticInnerSingleton() {}
// 静态内部类持有实例
private static class Holder {
static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
}
public static StaticInnerSingleton getInstance() {
return Holder.INSTANCE; // 调用时才加载Holder类
}
}
优点:
✅ 线程安全(JVM 保证类加载唯一)
✅ 延迟加载(按需创建)
五、进阶:枚举实现单例
最简实现:
enum EnumSingleton {
INSTANCE; // 枚举常量本身就是单例
public void doSomething() {
System.out.println("枚举单例执行操作");
}
}
特点:
✅ 天然线程安全
✅ 防止反射和序列化破坏单例(推荐场景)
六、应用场景与优缺点
🔥 常见应用场景
- 资源管理器:数据库连接池、线程池
- 全局状态:网站计数器、配置文件读取
- 工具类:日志记录器(如 Log4j)
⚠️ 注意事项
实现方式 | 线程安全 | 延迟加载 | 推荐场景 |
---|---|---|---|
饿汉式 | 是 | 否 | 实例占用资源少 |
双重检查锁 | 是 | 是 | 高并发场景 |
静态内部类 | 是 | 是 | 通用场景(推荐) |
枚举 | 是 | 否 | 防止反射 / 序列化攻击 |
七、初学者必避的 3 大陷阱(15 分钟)
陷阱 1:反射破坏单例
// 攻击代码
Class<?> clazz = HungrySingleton.class;
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton attackInstance = (HungrySingleton) constructor.newInstance();
解决方案:在构造器中添加检查
private HungrySingleton() {
if (instance != null) { // 防止反射攻击
throw new IllegalStateException("单例已存在");
}
}
陷阱 2:序列化破坏单例
现象:反序列化会创建新实例
解决方案:重写readResolve
方法
protected Object readResolve() {
return instance; // 返回原有实例
}
八、课堂练习
练习 1:实现线程安全的单例
需求:
- 用静态内部类方式实现
ConfigManager
单例 - 提供
getConfig(String key)
方法获取配置
练习 2:修复懒汉式线程安全问题
任务:将基础懒汉式改为双重检查锁版本,并测试多线程环境
九、课程总结
知识图谱:
单例模式 → 全局唯一实例
↳ 实现方式:饿汉式/懒汉式/DCL/静态内部类/枚举
↳ 核心要素:私有构造器、静态实例、全局方法
↳ 常见问题:线程安全、反射攻击、序列化
口诀记忆:
“单例要记三要素,私有构造加静态,
饿汉加载类先行,懒汉加锁按需生,
静态内部是优选,枚举安全又简单!”
十、课后作业
必做 1:实现数据库连接池单例
需求:
- 用静态内部类实现
DBConnectionPool
- 包含
getConnection()
和releaseConnection()
方法
必做 2:分析 JDK 中的单例
任务:查看Runtime
类源码,说明它是哪种单例实现