二十一篇,一条主线
整个系列其实只讲了一件事:并发三宗罪——原子性、可见性、有序性。第 1 篇的库存超卖是原子性,死循环收不到停止信号是可见性,半成品对象是有序性。后面讲的所有工具,都是围绕三宗罪的武器。收官这篇把它们串成一页纸的作战地图。
治原子性:锁与无锁两条路
单变量、低冲突——原子类直接上(AtomicInteger;高并发统计换 LongAdder;要治 ABA 加版本号);多变量要打包原子改,AtomicReference。一段逻辑整体互斥——默认 synchronized(简单、自动放锁、持续优化);需要可中断、可超时、公平、多条件之一,换 ReentrantLock;读多写少上读写锁,写极少的极端读多上 StampedLock 乐观读;读多写极少且是列表,CopyOnWriteArrayList;一张表的并发读写,ConcurrentHashMap(记住 putIfAbsent 这类原子组合方法,别先 get 再 put)。
治可见性与有序性:volatile 与 happens-before
状态开关、配置刷新这类一写多读——volatile 足矣;DCL 单例必须带 volatile(或者干脆用静态内部类)。遇到「另一个线程看不见」「偶现的灵异 Bug」,先问自己:线程之间的读写,happens-before 链条凑齐了吗?程序顺序、锁、volatile、start/join、传递性——五条规则里至少占一条,否则 JMM 不做任何承诺。
等待与协调:三件套、队列、编排
等多个一次性事件——CountDownLatch(countDown 记得放 finally);同伴互相等到齐——CyclicBarrier(可循环);限流与资源配额——Semaphore。生产者消费者直接 BlockingQueue(put/take 阻塞组为主,别手写 wait/notify);每个线程要独立一份数据——ThreadLocal(finally remove 铁律)。多个下游接口并行聚合——CompletableFuture(自定义线程池、异常兜底、orTimeout 超时)。高并发 IO 服务——评估虚拟线程(不池化,注意旧版本 synchronized pinning)。
线程管理:池的纪律
手动 new ThreadPoolExecutor,七个参数一个不少;任务旅程的顺序刻进脑子:核心线程 → 队列 → 非核心线程 → 拒绝策略;线程工厂必须起名;监控活跃数与积压;拒绝策略宁可吵着失败、不要安静地丢;公式给起点、压测给终点;参数外置做动态调优。
排障手册速记
jstack 三步:先看线程名(工厂起的名这时候救命);再看状态——BLOCKED 查锁竞争、WAITING 查谁该唤醒、RUNNABLE 别被 IO 阻塞骗了要结合栈帧看;最后查持锁链找死锁环。概率性 Bug 三板斧:压测放大流量、监控指标固化证据、二分缩小变更范围。加上全系列反复出现的三条纪律:锁与解锁严格配对、共享可变状态最小化、用成熟工具别手造轮子。
面试高频五连
顺手归拢系列覆盖的高频题:volatile 为什么不保证原子性;synchronized 锁升级的完整链路;AQS 的 state 与队列如何支撑几十个工具;线程池的任务旅程与参数怎么定;ThreadLocal 为什么内存泄漏。每一题都能对着前面的单篇展开十分钟——面试的底气来自体系,不来自背题。
写在最后
二十一篇完结。并发编程没有捷径,但有一条被反复验证的路:先把心智模型立对(三宗罪、JMM、AQS),再用工程纪律守住底线(配对、最小共享、用成熟工具),最后在实战里磨手感。愿你在下一次「概率性 Bug」面前,不再是靠重启碰运气的人。系列完结,下一个系列见。
评论 (0)