线程安全和无锁编程的关键在于理解场景、识别风险、选对工具并反复验证,而非堆砌代码;需从典型竞态(如非线程安全计数器)入手,对比synchronized、reentrantlock、atomicinteger等方案;按数据结构类型匹配并发工具;深入cas原理与aba问题;在转账、生产者-消费者等真实场景中锤炼锁设计能力。

练线程安全和无锁编程,关键不在堆代码量,而在理解场景、识别风险、选对工具并反复验证。光写个 synchronized 或 AtomicInteger 不代表你真会——得知道为什么用它、不用它会出什么问题、换种场景它还管不管用。
从典型竞态场景开始动手
别一上来就搞分布式或高并发系统,先用最朴素的“多线程改同一个数”把问题暴露出来:
- 写一个非线程安全的计数器(比如直接
int count++),启动10个线程各加1000次,最后结果大概率不是10000; - 用
jconsole或jstack观察线程状态,看是不是卡在WAITING或BLOCKED; - 再分别用
synchronized方法、synchronized代码块、ReentrantLock、AtomicInteger改一遍,对比执行时间和最终结果。
按数据结构类型匹配并发工具
不同共享数据,适用的线程安全方案差异很大,硬套会踩坑:
-
只读为主、偶有更新:用
ConcurrentHashMap替代HashMap + synchronized,避免整表锁; -
频繁遍历+少量修改:考虑
CopyOnWriteArrayList,但注意写操作开销大,不适合高频更新; -
队列类任务分发:优先用
ConcurrentLinkedQueue(无锁)或ArrayBlockingQueue(带锁但支持阻塞); -
状态标志位(如开关、初始化完成标记):用
volatile+ DCL(双重检查锁定),确保可见性和禁止重排序。
亲手拆解无锁逻辑,不止调 API
原子类不是黑盒,理解 CAS 才能判断它是否适用:
- 写个简易版
AtomicInteger模拟:用Unsafe.compareAndSwapInt实现自增,观察失败重试循环; - 故意制造 ABA 问题:让一个值从 1→2→1,看
AtomicStampedReference如何通过版本号解决; - 对比
AtomicInteger.updateAndGet和incrementAndGet的语义差异——前者支持复合运算,后者只做 +1。
用真实协作场景练锁设计
单个变量保护只是入门,复杂业务才是重点:
- 模拟转账:两个账户余额更新必须原子,尝试用单把锁(粗粒度)、两把锁(注意获取顺序)、
ReentrantLock.tryLock()防死锁; - 实现生产者-消费者:用
ReentrantLock + Condition替代wait/notify,体会显式条件等待的优势; - 加个超时控制:比如锁等待不超过 200ms,超时就放弃或降级处理,这是线上系统常见需求。
不复杂但容易忽略:每次改完代码,都该问一句——这个锁保护的是什么?有没有非共享操作被裹进去了?有没有更轻量的替代方案?练多了,自然就形成肌肉记忆。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











