1. 并发编程核心概念解析
当CPU从单核时代迈入多核架构时,并发编程就从选修课变成了程序员必修的生存技能。我处理过最典型的案例是一个交易系统,在单线程模式下每秒只能处理200笔订单,通过合理的并发改造后性能直接飙升到5000+TPS。这种质的飞跃正是并发编程的魅力所在。
并发(Concurrency)的本质是"逻辑上的同时处理",与并行(Parallelism)的"物理上的同时执行"形成互补。就像快餐店一个厨师交替做汉堡和薯条(并发),与两个厨师分别做汉堡和薯条(并行)的区别。在实际开发中,我们往往需要同时运用这两种技术。
2. 线程基础与内存模型
2.1 线程生命周期管理
新建(New) -> 就绪(Runnable) -> 运行(Running) -> 阻塞(Blocked) -> 终止(Terminated)这个经典状态机背后,隐藏着许多实践细节:
java复制Thread thread = new Thread(() -> {
// 任务逻辑
});
thread.start(); // 启动线程的正确方式
thread.join(); // 等待线程结束
警告:直接调用run()方法是新手常见错误,这会导致方法在当前线程同步执行,失去了多线程意义
2.2 Java内存模型(JMM)深度解读
JMM定义了线程与主内存的交互规则,其核心是happens-before原则。我曾遇到过一个诡异的bug:在开发者的高性能笔记本上运行正常的程序,到了生产环境就出现数据错乱。最终发现是缺少volatile修饰符导致的内存可见性问题。
java复制class SharedData {
private volatile boolean flag = false; // 保证多线程可见性
// ...
}
3. 并发工具库实战
3.1 锁的进化论
从原始的synchronized到JUC包中的Lock接口,锁技术经历了三次重要进化:
- 内置锁(synchronized)
- 显式锁(ReentrantLock)
- 读写锁(ReentrantReadWriteLock)
java复制Lock lock = new ReentrantLock();
try {
lock.lock();
// 临界区代码
} finally {
lock.unlock(); // 必须手动释放
}
3.2 并发容器性能对比
在电商系统压测中,不同并发容器的表现差异惊人:
| 容器类型 | 写入性能(ops/ms) | 读取性能(ops/ms) | 线程安全 |
|---|---|---|---|
| HashMap | 1256 | 8542 | 否 |
| Hashtable | 342 | 2156 | 是 |
| ConcurrentHashMap | 986 | 7985 | 是 |
4. 线程池工程实践
4.1 参数调优黄金法则
线程池配置不当导致的生产事故我见过太多。核心参数关系可以用这个公式表示:
code复制最大并发数 = (任务数 × 平均耗时) / 容忍时间
实际配置示例:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // 核心线程数
16, // 最大线程数
60, // 空闲超时(秒)
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
4.2 资源泄漏排查实录
某次线上OOM事故的排查过程:
- 通过jstack发现大量线程处于WAITING状态
- jmap导出堆内存分析,发现未关闭的数据库连接
- 最终定位到未正确关闭的ThreadLocal资源
关键技巧:使用try-with-resources确保资源释放
5. 异步编程新模式
5.1 CompletableFuture组合式编程
电商订单处理链路的异步改造案例:
java复制CompletableFuture.supplyAsync(this::checkInventory)
.thenApplyAsync(this::calculatePrice)
.thenCombineAsync(
getDiscountAsync(userId),
(price, discount) -> price.multiply(discount))
.thenAccept(this::sendNotification);
5.2 响应式编程背压机制
当生产者速度 > 消费者速度时,RxJava的背压策略对比:
| 策略 | 内存占用 | 数据丢失风险 | 适用场景 |
|---|---|---|---|
| BUFFER | 高 | 低 | 短期突发流量 |
| DROP | 低 | 高 | 实时性要求不高 |
| LATEST | 中 | 中 | 只需最新数据 |
| ERROR | 低 | 立即失败 | 严格流量控制 |
6. 并发调试高级技巧
6.1 死锁四步定位法
- jps获取Java进程ID
- jstack生成线程转储
- 查找"deadlock"关键词
- 分析相互等待的锁资源
典型死锁模式:
code复制Thread A持有锁X,等待锁Y
Thread B持有锁Y,等待锁X
6.2 并发单元测试策略
使用CountDownLatch模拟并发场景:
java复制@Test
public void testConcurrentAccess() throws InterruptedException {
final int threadCount = 100;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
// 测试逻辑
latch.countDown();
}).start();
}
latch.await(10, TimeUnit.SECONDS);
// 验证结果
}
7. 性能优化实战记录
7.1 锁粒度优化案例
优化前:
java复制synchronized void processOrder(Order order) {
// 整个方法加锁
}
优化后:
java复制void processOrder(Order order) {
synchronized(order.getId().intern()) { // 细粒度锁
// 核心逻辑
}
}
7.2 无锁编程实践
使用AtomicLong实现计数器:
java复制private final AtomicLong counter = new AtomicLong();
public long increment() {
return counter.incrementAndGet();
}
性能对比:
- 锁方案:12,000 ops/sec
- CAS方案:850,000 ops/sec
8. 分布式并发挑战
8.1 分布式锁实现方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 数据库乐观锁 | 实现简单 | 性能差 | 低频竞争场景 |
| Redis SETNX | 性能好 | 锁续期复杂 | 短时任务 |
| Zookeeper | 可靠性高 | 部署复杂 | 金融级系统 |
| Redisson | 功能完善 | 依赖Redis | 大多数分布式场景 |
8.2 幂等性设计模式
电商支付接口的幂等实现:
java复制public Result processPayment(String requestId, Payment payment) {
// 1. 检查请求ID是否已处理
if (idempotentCache.contains(requestId)) {
return cache.get(requestId);
}
// 2. 执行业务逻辑
Result result = realPaymentService.process(payment);
// 3. 缓存结果
idempotentCache.put(requestId, result);
return result;
}
在多年并发编程实践中,最深刻的体会是:并发问题就像幽灵,在测试环境可能完全正常,到了生产环境才会突然现身。建议每个重要并发模块都进行:
- 压力测试(模拟10倍峰值流量)
- 混沌测试(随机kill线程)
- 长时间运行测试(检查资源泄漏)
