引言:一个线上事故引发的思考
上周我们的电商系统遭遇了严重事故:促销活动期间订单服务积压,最终导致JVM OOM崩溃。根本原因竟是订单处理模块的生产者-消费者设计缺陷!本文将通过这个真实案例,深入剖析Java多线程核心概念,手把手实现高性能生产者-消费者模型,并分享性能优化实战经验。
一、问题重现:订单系统崩溃现场
异常堆栈关键信息
java
复制
下载
java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
at java.lang.StringBuilder.append(StringBuilder.java:208)
// 业务代码定位到订单队列操作处
监控数据揭示真相
| 指标 | 正常值 | 事故时值 |
|---|---|---|
| 订单队列长度 | 0-500 | 82,457 |
| CPU使用率 | 30%-60% | 98% |
| GC频率 | 5次/分钟 | 200次/分钟 |
根本原因:生产者(订单接收)和消费者(订单处理)速度不匹配,导致阻塞队列堆积撑爆内存
二、核心概念解析:多线程易混淆点
1. synchronized vs ReentrantLock
java
复制
下载
// synchronized实现
public synchronized void addOrder(Order order) {
queue.add(order);
}
// ReentrantLock实现
private final Lock lock = new ReentrantLock(true); // 公平锁
public void addOrder(Order order) {
lock.lock();
try {
queue.add(order);
} finally {
lock.unlock();
}
}
对比决策表:
| 场景 | 推荐方案 |
|---|---|
| 简单同步 | synchronized |
| 需要尝试获取锁 | ReentrantLock |
| 需要公平锁 | ReentrantLock |
| 需要锁状态监控 | ReentrantLock |
2. wait/notify vs Condition
java
复制
下载
// wait/notify实现
public synchronized Order get() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 释放锁等待
}
return queue.remove();
}
// Condition实现
private final Condition notEmpty = lock.newCondition();
public Order get() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 更细粒度的等待
}
return queue.remove();
} finally {
lock.unlock();
}
}
关键区别:Condition支持多个等待队列,可精确唤醒特定类型线程
三、解决方案:生产者-消费者模型重构
类图设计
图表
代码
下载
依赖
依赖
OrderProducer
+submitOrder(Order order)
OrderConsumer
+processOrder()
OrderQueue
-BlockingQueue queue
+put(Order order)
+take() : Order
核心代码实现
java
复制
下载
public class OrderQueue {
private final BlockingQueue<Order> queue;
private final AtomicInteger overflowCount = new AtomicInteger(0);
public OrderQueue(int capacity) {
this.queue = new ArrayBlockingQueue<>(capacity);
}
public boolean put(Order order, long timeout) throws InterruptedException {
return queue.offer(order, timeout, TimeUnit.MILLISECONDS);
}
public Order take() throws InterruptedException {
return queue.take();
}
// 监控方法
public int getQueueSize() {
return queue.size();
}
}
消费者线程池优化
java
复制
下载
int coreSize = Runtime.getRuntime().availableProcessors();
ExecutorService consumerPool = new ThreadPoolExecutor(
coreSize,
coreSize * 2,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
new CustomThreadFactory("order-consumer"),
new ThreadPoolExecutor.CallerRunsPolicy() // 重要!防止任务丢弃
);
四、性能优化实战:从崩溃到支撑百万订单
优化前后对比
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 最大吞吐量 | 120单/秒 | 8,500单/秒 |
| 99%响应延迟 | 2.4秒 | 86毫秒 |
| GC暂停时间 | 1.2秒/次 | 200毫秒/次 |
关键优化点
-
队列选择:
java
复制
下载
// 替换LinkedList为有界队列 new ArrayBlockingQueue<>(2000)
-
背压机制:
java
复制
下载
// 生产者添加超时控制 if (!queue.put(order, 100)) { metrics.markRejectedOrder(); throw new BusyException("系统繁忙,请稍后重试"); } -
消费者动态扩缩容:
java
复制
下载
// 根据队列负载调整线程数 if (queue.size() > WARN_THRESHOLD) { consumerPool.setCorePoolSize(coreSize * 2); } -
监控埋点:
java
复制
下载
// Micrometer监控队列 Gauge.builder("order.queue.size", queue::size) .tag("module", "order") .register(registry);
五、避坑指南:多线程开发常见陷阱
1. 死锁场景重现
java
复制
下载
// 错误代码:嵌套锁顺序不一致 Thread1: lockA.lock(); lockB.lock(); Thread2: lockB.lock(); lockA.lock(); // 可能死锁
解决方案:
java
复制
下载
// 使用ReentrantLock的tryLock
if (lockA.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// ...
} finally { lockB.unlock(); }
}
} finally { lockA.unlock(); }
}
2. 线程上下文丢失
java
复制
下载
// MDC在异步线程中丢失
MDC.put("traceId", "12345");
executor.submit(() -> {
// 这里MDC为空!
processOrder();
});
修复方案:
java
复制
下载
// 使用装饰器传递上下文
public class MdcAwareRunnable implements Runnable {
private final Map<String, String> contextMap;
private final Runnable runnable;
public MdcAwareRunnable(Runnable runnable) {
this.contextMap = MDC.getCopyOfContextMap();
this.runnable = runnable;
}
@Override
public void run() {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
runnable.run();
} finally {
MDC.clear();
}
}
}
六、经验总结:多线程编程最佳实践
-
锁使用原则:
-
尽量缩小同步范围
-
读写分离场景用
ReentrantReadWriteLock -
无竞争时优先
volatile
-
-
线程池配置黄金法则:
-
CPU密集型:线程数 = CPU核数 + 1
-
IO密集型:线程数 = CPU核数 * (1 + 平均等待时间/平均计算时间)
-
-
故障预防三板斧:
java
复制
下载
// 1. 队列必须有界 new ArrayBlockingQueue<>(1000) // 2. 添加拒绝策略 new ThreadPoolExecutor.CallerRunsPolicy() // 3. 重要任务持久化 if (!queue.offer(task)) { taskStore.save(task); // 存储到数据库 } -
调试技巧:
bash
复制
下载
# 查看线程状态 jstack <pid> | grep -A 20 "java.lang.Thread.State" # 监控锁竞争 jcmd <pid> Thread.print -l

被折叠的 条评论
为什么被折叠?



