1. 为什么每个Java开发者都需要掌握多线程
记得刚工作那会儿,我接手了一个简单的订单处理系统。在测试环境跑得好好的程序,一上线就频繁崩溃。排查了三天才发现,当并发用户超过50时,系统就会因为线程阻塞而雪崩。这个惨痛教训让我明白:在Java世界里,不会多线程就等于不会游泳却要横渡长江。
现代Java应用几乎都绕不开并发编程。从Web服务器的请求处理到分布式系统的消息消费,从大数据处理到高频交易系统,多线程就像空气一样无处不在。但很多开发者对线程的理解还停留在Thread和Runnable的层面,这就像拿着木棍参加现代战争。
2. 线程基础:从玩具模型到生产级代码
2.1 线程生命周期深度解析
教科书上那个简单的线程状态图(New/Runnable/Blocked/Waiting/Timed_Waiting/Terminated)在实际开发中远不够用。比如BLOCKED状态至少包含:
- 同步阻塞(synchronized)
- IO阻塞(Socket读写)
- 锁阻塞(LockSupport.park)
// 典型错误示例 - 以为sleep会让出锁 synchronized(lock) { Thread.sleep(1000); // 实际上锁仍然被持有! }关键认知:sleep()不会释放任何锁资源,而wait()会释放synchronized持有的锁
2.2 创建线程的四种方式对比
- 继承Thread类:适合简单demo,但违反了组合优于继承原则
- 实现Runnable:解决了单继承限制,但没有返回值
- 实现Callable:可以返回结果和抛出异常,配合Future使用
- 线程池提交:生产环境唯一推荐方式
// 使用FutureTask的正确姿势 FutureTask<Integer> task = new FutureTask<>(() -> { TimeUnit.SECONDS.sleep(2); return 42; }); new Thread(task).start(); System.out.println(task.get()); // 阻塞直到获取结果3. 线程安全:从原子性到内存屏障
3.1 synchronized的隐藏细节
很多人以为synchronized只是简单的互斥锁,其实它包含三个重要特性:
- 原子性(Atomicity)
- 可见性(Visibility)
- 有序性(Ordering)
class Counter { private int count; // 实例方法锁的是this对象 public synchronized void increment() { count++; } // 静态方法锁的是Class对象 public static synchronized void reset() { // ... } }3.2 volatile的适用场景
volatile最适合的状态标志模式:
class Worker implements Runnable { private volatile boolean running = true; public void stop() { running = false; } @Override public void run() { while(running) { // 执行任务 } } }但volatile不能保证复合操作的原子性:
volatile int count = 0; count++; // 这不是原子操作!4. JUC工具库实战指南
4.1 ReentrantLock的进阶用法
相比synchronized,ReentrantLock提供了:
- 可中断的锁获取
- 超时获取锁
- 公平锁选项
- 条件变量支持
Lock lock = new ReentrantLock(); Condition notEmpty = lock.newCondition(); void consume() throws InterruptedException { lock.lock(); try { while(queue.isEmpty()) { notEmpty.await(); // 释放锁并等待 } // 消费元素 } finally { lock.unlock(); } }4.2 并发容器性能对比
| 容器类型 | 线程安全实现 | 适用场景 |
|---|---|---|
| HashMap | 不安全 | 单线程环境 |
| Hashtable | 全表锁 | 已淘汰 |
| ConcurrentHashMap | 分段锁+CAS | 高并发读写 |
| CopyOnWriteArrayList | 写时复制 | 读多写少 |
| ArrayBlockingQueue | ReentrantLock+Condition | 生产者消费者模型 |
5. 线程池的七个核心参数
理解ThreadPoolExecutor的构造参数:
new ThreadPoolExecutor( corePoolSize, // 常驻核心线程数 maximumPoolSize, // 最大线程数 keepAliveTime, // 空闲线程存活时间 unit, // 时间单位 workQueue, // 任务队列 threadFactory, // 线程创建工厂 handler // 拒绝策略 );四种拒绝策略对比:
- AbortPolicy(默认):直接抛出RejectedExecutionException
- CallerRunsPolicy:用调用者线程执行任务
- DiscardPolicy:静默丢弃任务
- DiscardOldestPolicy:丢弃队列最老的任务
6. 死锁诊断与预防实战
6.1 死锁产生的四个必要条件
- 互斥条件
- 请求与保持
- 不可剥夺
- 循环等待
6.2 使用jstack诊断死锁
jstack <pid> | grep -A 10 "deadlock"6.3 锁顺序死锁案例
// 错误写法:可能产生死锁 void transfer(Account from, Account to, int amount) { synchronized(from) { synchronized(to) { // 转账操作 } } } // 正确写法:统一锁顺序 private static final Object tieLock = new Object(); void transfer(Account from, Account to, int amount) { int fromHash = System.identityHashCode(from); int toHash = System.identityHashCode(to); if(fromHash < toHash) { synchronized(from) { synchronized(to) { // 转账操作 } } } else if(fromHash > toHash) { synchronized(to) { synchronized(from) { // 转账操作 } } } else { synchronized(tieLock) { synchronized(from) { synchronized(to) { // 转账操作 } } } } }7. 并发编程性能优化技巧
7.1 减少锁竞争的方法
- 缩小同步代码块范围
- 降低锁粒度(如ConcurrentHashMap的分段锁)
- 使用读写锁(ReentrantReadWriteLock)
- 尝试无锁编程(Atomic变量类)
7.2 线程池调优经验
- CPU密集型任务:线程数 = CPU核数 + 1
- IO密集型任务:线程数 = CPU核数 * (1 + 平均等待时间/平均计算时间)
- 混合型任务:拆分为CPU密集和IO密集两个线程池
// 获取CPU核心数 int cores = Runtime.getRuntime().availableProcessors(); // 最佳线程数估算公式 int threadCount = cores * (1 + (平均IO时间/平均CPU时间));8. CompletableFuture异步编程
传统Future的局限性:
- 无法手动完成
- 缺乏回调机制
- 不能链式调用
CompletableFuture的典型用法:
CompletableFuture.supplyAsync(() -> queryFromDB()) .thenApplyAsync(data -> processData(data)) .thenAcceptAsync(result -> saveToCache(result)) .exceptionally(ex -> { log.error("处理失败", ex); return null; });组合多个Future的常用模式:
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2); all.thenRun(() -> { // 所有任务完成后的处理 }); CompletableFuture<Object> any = CompletableFuture.anyOf(future1, future2); any.thenAccept(result -> { // 任意任务完成后的处理 });9. 线程本地存储ThreadLocal的陷阱
9.1 内存泄漏问题
ThreadLocal的经典内存泄漏场景:
public class UserHolder { private static ThreadLocal<User> holder = new ThreadLocal<>(); public static void set(User user) { holder.set(user); } // 忘记调用remove()! }正确用法:
try { UserHolder.set(currentUser); // 执行业务逻辑 } finally { UserHolder.remove(); // 必须清理 }9.2 InheritableThreadLocal的局限性
子线程可以继承父线程的ThreadLocal值,但在线程池中会失效:
InheritableThreadLocal<String> context = new InheritableThreadLocal<>(); context.set("parent"); // 线程池复用线程时不会重新继承 executor.submit(() -> { System.out.println(context.get()); // 可能为null });10. 实战:设计一个高并发订单系统
10.1 订单状态机设计
public enum OrderState { INIT { @Override public void next(Order order) { order.setState(PAID); } }, PAID { @Override public void next(Order order) { if(inventoryCheck()) { order.setState(SHIPPED); } } }, // 其他状态... public abstract void next(Order order); }10.2 库存扣减方案对比
方案一:数据库乐观锁
UPDATE inventory SET stock = stock - 1 WHERE product_id = ? AND stock >= 1方案二:Redis原子操作
redisTemplate.opsForValue().increment("product:1", -1);方案三:分布式锁
try { if(redisLock.tryLock("product_1", 10, TimeUnit.SECONDS)) { // 扣减库存 } } finally { redisLock.unlock(); }11. 多线程调试技巧
11.1 线程命名规范
ThreadFactory factory = r -> { Thread t = new Thread(r); t.setName("order-processor-" + counter.getAndIncrement()); return t; };11.2 使用ThreadMXBean监控
ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] threadIds = bean.findDeadlockedThreads(); if(threadIds != null) { ThreadInfo[] infos = bean.getThreadInfo(threadIds); // 打印死锁信息 }12. Java内存模型(JMM)核心原理
12.1 happens-before规则
- 程序顺序规则
- 锁规则
- volatile变量规则
- 线程启动规则
- 线程终止规则
- 中断规则
- 终结器规则
- 传递性
12.2 内存屏障类型
- LoadLoad屏障
- StoreStore屏障
- LoadStore屏障
- StoreLoad屏障
// 伪代码展示volatile写的内存屏障 volatile boolean flag = false; x = 42; flag = true; // 插入StoreStore + StoreLoad屏障13. 常见并发问题排查案例
13.1 CPU占用过高排查
- top -Hp找出高CPU线程
- jstack获取线程堆栈
- 结合十六进制线程ID分析
13.2 线程阻塞分析
jstack <pid> | grep -A 20 "BLOCKED"13.3 内存泄漏定位
- jmap生成堆转储
- MAT分析对象引用链
- 重点关注ThreadLocal和静态集合
14. 并发测试工具集
14.1 JMH基准测试
@BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) public class LockBenchmark { @Benchmark public void testSynchronized() { synchronized(this) { // 测试代码 } } @Benchmark public void testReentrantLock() { lock.lock(); try { // 测试代码 } finally { lock.unlock(); } } }14.2 并发压力测试
- 使用CountDownLatch模拟并发
- JMeter分布式压测
- ChaosBlade注入故障
15. Java并发演进路线
- Java 5:JUC工具包诞生(线程池、并发集合、原子类)
- Java 7:Fork/Join框架
- Java 8:CompletableFuture、StampedLock
- Java 9:响应式流API
- Java 11:HTTP/2客户端支持异步
- Java 19:虚拟线程(预览)
虚拟线程使用示例:
try(var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i -> { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); }在真实项目中,我发现最容易被忽视的是线程池的队列选择。曾经有个系统使用无界队列导致OOM,后来改用SynchronousQueue配合合适的拒绝策略,系统稳定性大幅提升。另一个经验是:对于需要传递上下文信息的场景,可以考虑使用ThreadLocal+MDC(Mapped Diagnostic Context)的组合,既保证线程安全又便于日志追踪。