一、不可变对象模式
1.1 不可变对象的设计
/**
* 标准的不可变对象设计
*/
public final class ImmutableUser {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutableUser(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
// 防御性复制
this.hobbies = new ArrayList<>(hobbies);
}
public String getName() { return name; }
public int getAge() { return age; }
public List<String> getHobbies() {
// 返回不可变视图
return Collections.unmodifiableList(hobbies);
}
}
/*
不可变对象的五大要求:
1. ✅ 类声明为final
2. ✅ 所有字段都是final
3. ✅ 所有字段都是private
4. ✅ 不提供setter方法
5. ✅ 可变字段防御性复制
优势:
├─ 天然线程安全
├─ 可以安全共享
└─ 适合做缓存key
适用场景:String、Integer、LocalDate等
*/
二、ThreadLocal模式
2.1 基本使用
/**
* ThreadLocal:线程本地存储
*/
public class ThreadLocalExample {
// 每个线程独立的SimpleDateFormat
private static ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static String formatDate(Date date) {
return dateFormat.get().format(date); // 线程安全
}
public static void main(String[] args) {
// 10个线程并发格式化日期
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(formatDate(new Date()));
}).start();
}
}
}
/*
使用场景:
1. SimpleDateFormat(线程不安全)
2. 数据库连接(每个线程独立连接)
3. 用户上下文(Spring Security)
*/
2.2 内存泄漏问题
/**
* ThreadLocal内存泄漏问题
*/
public class ThreadLocalMemoryLeak {
private static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 线程池场景
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 存储大对象
threadLocal.set(new byte[1024 * 1024]); // 1MB
// 业务逻辑
// ...
// ❌ 忘记清理,导致内存泄漏
// threadLocal.remove(); // 正确做法
});
}
executor.shutdown();
}
/*
内存泄漏原因:
1. ThreadLocal存储在ThreadLocalMap中
2. key是弱引用(ThreadLocal)
3. value是强引用(实际对象)
4. 线程池线程复用,ThreadLocalMap一直存在
5. value无法被GC,导致内存泄漏
解决方案:
try {
threadLocal.set(value);
// 业务逻辑
} finally {
threadLocal.remove(); // 必须清理
}
*/
}
三、两阶段终止模式
3.1 优雅停止线程
/**
* 正确的线程停止方式
*/
public class GracefulShutdown {
private volatile boolean running = true;
public void start() {
Thread thread = new Thread(() -> {
while (running) { // 检查标志位
try {
// 业务逻辑
Thread.sleep(1000);
} catch (InterruptedException e) {
// 响应中断
Thread.currentThread().interrupt();
break;
}
}
// 清理资源
cleanup();
});
thread.start();
}
public void stop() {
running = false; // 第一阶段:设置标志位
}
private void cleanup() {
// 第二阶段:清理资源
System.out.println("清理资源");
}
/*
错误做法:
thread.stop(); // ❌ 已废弃,不安全
正确做法:
1. volatile标志位
2. interrupt()中断
3. 两阶段:设置标志 + 清理资源
*/
}
四、读写锁模式
4.1 ReentrantReadWriteLock
/**
* 读写锁:读多写少场景
*/
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Map<String, String> cache = new HashMap<>();
// 读操作:共享锁
public String get(String key) {
rwLock.readLock().lock();
try {
return cache.get(key);
} finally {
rwLock.readLock().unlock();
}
}
// 写操作:排他锁
public void put(String key, String value) {
rwLock.writeLock().lock();
try {
cache.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
/*
读写锁特性:
├─ 读-读:可并发
├─ 读-写:互斥
└─ 写-写:互斥
性能提升:
读多写少场景,性能比synchronized高3-5倍
*/
}
4.2 StampedLock(JDK 8)
/**
* StampedLock:性能更好的读写锁
*/
public class StampedLockExample {
private final StampedLock lock = new StampedLock();
private double x, y;
// 乐观读
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead(); // 乐观读
double currentX = x;
double currentY = y;
if (!lock.validate(stamp)) { // 验证是否被修改
stamp = lock.readLock(); // 升级为悲观读锁
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
// 写锁
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}
/*
StampedLock优势:
1. 乐观读不加锁(性能极高)
2. 读写性能比ReentrantReadWriteLock高
3. 适合读多写少场景
注意:
❌ 不支持重入
❌ 不支持Condition
*/
}
五、并发编程最佳实践
5.1 核心原则
1. 优先使用不可变对象
└─ String、Integer、LocalDate
2. 减少锁的范围
└─ 只锁必要的代码
3. 避免锁嵌套
└─ 容易死锁
4. 使用并发工具类
├─ AtomicInteger替代synchronized计数
├─ ConcurrentHashMap替代Hashtable
├─ CountDownLatch/CyclicBarrier协调线程
└─ BlockingQueue实现生产者-消费者
5. 线程池管理
├─ 自定义ThreadPoolExecutor(禁用Executors)
├─ 优雅关闭(shutdown + awaitTermination)
└─ 线程命名(便于排查)
6. 异常处理
├─ 捕获InterruptedException
├─ 设置UncaughtExceptionHandler
└─ 避免异常导致线程死亡
5.2 常见陷阱
/**
* 常见并发陷阱
*/
public class ConcurrencyTraps {
// ❌ 陷阱1:双重检查锁定错误
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 需要volatile
}
}
}
return instance;
}
// ✅ 正确:private static volatile Singleton instance;
// ❌ 陷阱2:SimpleDateFormat共享
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// ✅ 正确:使用ThreadLocal或DateTimeFormatter
// ❌ 陷阱3:复合操作不原子
if (!map.containsKey(key)) {
map.put(key, value);
}
// ✅ 正确:map.putIfAbsent(key, value);
// ❌ 陷阱4:忘记释放锁
lock.lock();
// 业务逻辑
lock.unlock(); // 异常时不会执行
// ✅ 正确:try-finally
// ❌ 陷阱5:使用Executors工厂方法
Executors.newFixedThreadPool(10); // 无界队列
// ✅ 正确:自定义ThreadPoolExecutor
static class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() { return instance; }
}
}
5.3 性能调优
1. 减少锁竞争
├─ 缩小锁范围
├─ 降低锁粒度(分段锁)
└─ 使用无锁算法(CAS)
2. 减少上下文切换
├─ 控制线程数(CPU核心数 × 2)
├─ 使用协程/虚拟线程(JDK 19+)
└─ 避免频繁阻塞
3. 内存优化
├─ 对象池(避免频繁创建)
├─ ThreadLocal注意清理
└─ 合理设置JVM参数
4. 监控与调优
├─ JProfiler监控线程状态
├─ Arthas诊断死锁
└─ 压测验证性能
六、总结:并发编程知识体系
6.1 核心知识点回顾
第1篇:并发三大问题
├─ 可见性:CPU缓存导致
├─ 原子性:指令交错执行
├─ 有序性:指令重排序
└─ JMM:happens-before规则
第2篇:同步机制
├─ synchronized:锁升级(偏向锁→轻量级锁→重量级锁)
├─ Lock:ReentrantLock、公平锁/非公平锁
├─ AQS:CLH队列、独占/共享模式
└─ 死锁:四个必要条件、预防策略
第3篇:无锁编程
├─ CAS:CPU的CMPXCHG指令
├─ Atomic:AtomicInteger、LongAdder
├─ 并发工具:CountDownLatch、Semaphore
└─ 无锁数据结构:无锁栈、无锁队列
第4篇:线程池与异步
├─ ThreadPoolExecutor:七参数、四拒绝策略
├─ ForkJoinPool:工作窃取算法
├─ CompletableFuture:组合式异步编程
└─ 最佳实践:配置、监控、优雅关闭
第5篇:并发集合
├─ ConcurrentHashMap:分段锁→CAS+synchronized
├─ BlockingQueue:生产者-消费者模式
├─ CopyOnWriteArrayList:写时复制
└─ 选择指南:性能对比、适用场景
第6篇:设计模式与实践
├─ 不可变对象:天然线程安全
├─ ThreadLocal:线程本地存储
├─ 读写锁:读多写少优化
└─ 最佳实践:避坑指南、性能调优
6.2 学习路径建议
阶段1:理解原理(1-2周)
├─ JMM、happens-before
├─ 三大问题
└─ volatile、final
阶段2:掌握同步(2-3周)
├─ synchronized原理
├─ Lock接口
└─ AQS源码
阶段3:无锁编程(2-3周)
├─ CAS原理
├─ Atomic类
└─ 并发工具类
阶段4:工程实践(4-6周)
├─ 线程池配置
├─ 并发集合使用
├─ 实际项目应用
└─ 性能调优
总计:约3个月系统学习
6.3 推荐资源
经典书籍:
1. 《Java并发编程实战》 ⭐⭐⭐⭐⭐
2. 《Java并发编程的艺术》 ⭐⭐⭐⭐
3. 《深入理解Java虚拟机》 ⭐⭐⭐⭐⭐
官方文档:
1. JDK源码(java.util.concurrent包)
2. Java Language Specification - Chapter 17
3. JSR-133: Java Memory Model
开源项目:
1. Netty(高性能网络框架)
2. Disruptor(高性能队列)
3. Spring(DI容器、事务管理)
结语
Java并发编程系列完结
从并发三大问题到设计模式最佳实践,我们完整地探索了Java并发编程的知识体系:
- 第一性原理:从"为什么"出发,理解本质
- 渐进式学习:从原理到实践,循序渐进
- 真实案例:秒杀、缓存、线程池,贴近实战
- 性能数据:量化收益,理性决策
核心收获:
- 理论基础:JMM、happens-before、三大问题
- 同步机制:synchronized、Lock、AQS
- 无锁编程:CAS、Atomic、并发工具类
- 资源管理:线程池、CompletableFuture
- 并发集合:ConcurrentHashMap、BlockingQueue
- 最佳实践:设计模式、避坑指南
下一步建议:
- ✅ 阅读JDK并发包源码
- ✅ 实际项目中应用
- ✅ 性能压测与调优
- ✅ 关注JDK新特性(虚拟线程等)
并发编程是一门艺术,需要理论与实践相结合。希望这个系列能帮助你建立完整的并发编程知识体系!
系列文章(全部完结):
- ✅ Java并发第一性原理:为什么并发如此困难?
- ✅ 线程安全与同步机制:从synchronized到Lock的演进
- ✅ 并发工具类与原子操作:无锁编程的艺术
- ✅ 线程池与异步编程:从Thread到CompletableFuture的演进
- ✅ 并发集合:从HashMap到ConcurrentHashMap的演进
- ✅ 并发设计模式与最佳实践(本文)
系列统计:
- 总字数:约90,000字
- 文章数:6篇
- 代码示例:100+个
- 涵盖主题:从JMM到最佳实践的完整体系
感谢阅读!如果这个系列对你有帮助,欢迎分享给更多的Java开发者!