Java JUC 锁与原子类
java.util.concurrent 中的显式锁与原子类:相对 synchronized 的能力边界与适用场景。
#concept
#growing
#dev
#java
[!info] related notes
Java JUC 锁与原子类
这个概念为什么出现
synchronized 简洁,但缺少超时获取、可中断获取、多条件队列等能力。java.util.concurrent.locks 与原子类提供更细控制与无锁算法基础(CAS)。
[!abstract] 一句话理解
ReentrantLock等提供显式加解锁与高级特性;原子类提供单变量操作的原子性,CAS 是其中一种更新方式;选它们是因为需求超出内置锁或单变量热点更新。
最小可运行示例
场景:抢券用原子计数;复杂临界区用 ReentrantLock 超时
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
public class CouponRace {
private final AtomicInteger remaining = new AtomicInteger(1);
private final ReentrantLock lock = new ReentrantLock();
boolean tryTakeAtomic() {
while (true) {
int cur = remaining.get();
if (cur <= 0) return false;
if (remaining.compareAndSet(cur, cur - 1)) return true;
}
}
boolean tryCriticalWork() throws InterruptedException {
if (lock.tryLock(50, TimeUnit.MILLISECONDS)) {
try {
return true; // 模拟临界区
} finally {
lock.unlock();
}
}
return false;
}
public static void main(String[] args) throws Exception {
CouponRace r = new CouponRace();
System.out.println(r.tryTakeAtomic());
System.out.println(r.tryTakeAtomic());
System.out.println(r.tryCriticalWork());
}
}
建议运行:
javac CouponRace.java && java CouponRace
期望输出:
true
false
true
结合场景再看三个关注点
- CAS 循环处理竞争。
- 成功获取锁后,unlock 放在 finally;tryLock 返回 false 时不能解锁。\
- 超时只限制获取锁的等待,不限制进入临界区后的工作耗时。
核心概念与准确模型
1. ReentrantLock
- 可重入
- 公平/非公平
lockInterruptibly/tryLock- 多
Condition
2. 读写锁
ReadWriteLock:读多写少场景(先测量)
3. 原子类
- AtomicInteger/AtomicReference 提供原子读写与 CAS 等操作。
- LongAdder 面向高并发统计,sum 不是并发更新下的原子快照,不能用“sum 后判断再递减”实现限额抢券。\
- 单变量更新;复杂不变量仍要锁或无锁结构
边界情况与反直觉行为
- 忘记 unlock → 永久阻塞。
- CAS ABA 问题(需要时用
AtomicStampedReference)。 - 公平锁吞吐可能更差,也不保证线程调度公平;ReentrantLock 的无参 tryLock 不遵守公平排队策略。
- updateAndGet 等函数式原子更新在竞争时可能重复执行更新函数,函数应无副作用。\
常见误区
[!warning] 常见误区:所有地方替换为 ReentrantLock 无额外需求时 synchronized 更简单;可读性也是正确性的一部分。
工程实践
- 锁对象私有。
- 统计性热点计数可考虑 LongAdder;需要精确读改写或 CAS 条件更新时用合适的原子变量/锁。\
- 与业务事务边界对齐,避免锁跨越远程调用。
- 死锁诊断用线程转储。
本节总结
- 显式锁=更多控制
- 原子变量提供单变量操作保证,不能自动保护多个字段或外部副作用\
- 简单场景不炫技
自测题
- 何时用 tryLock 超时?
- AtomicInteger 能否保护“扣库存并记日志两步不变量”?
参考答案
- 避免死等、实现降级快速失败。
- 不能单独保证多步骤不变量,需要更高层同步/事务设计。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| java.util.concurrent.locks | API | 锁 |
| Atomic variables tutorial | 教程 | 原子变量 |