《当 synchronized 遇上 ReentrantLock:一场守门人的革命》
🏮 故事开始:一座“共享金库”的江湖
想象有一座金库(代表共享资源,比如一个变量、一个文件、一个数据库连接),很多人(线程)都想进去拿钱。但规矩是:同一时间只能有一个人进去,否则会打起来!
于是,江湖上出现了几种“守门人”制度,来管理谁先进、谁后进、谁等一等……
第一章:最早的守门人 —— 管程(Monitor)
很久以前,有个叫 管程 的守门人,他站在金库门口,手里拿着一把钥匙。
- 谁想进?先找他登记。
- 他给第一个人开门,其他人就在门外排队。
- 但有时候,进去的人发现:“哎呀,我要的钱还没到账,得等通知!”→ 他就对管程说:“我先出去等,等有人通知我到账了再进来。”
- 管程就把他安排到一个专门的等候区(这叫“条件队列”)。
- 等别人喊一声“到账了!”,管程就把那人从等候区叫回来,重新排队进门。
🔔 这就是 Java 里
synchronized + wait()/notify()的原理!它用的是 MESA 模型:你被唤醒后,还得重新排队抢门(不能插队直接进)。
但问题来了:这个管程太死板!
- 只有一个等候区(一个条件变量);
- 不能打断别人等待;
- 不能设置“等 5 分钟没人叫我我就走”;
- 排队也不公平(后来者可能插队)。
于是,江湖上出现了更聪明的守门人……
第二章:新一代守门人 —— AQS(抽象队列同步器)
AQS 不是一个具体的人,而是一套守门人培训手册。
任何想当守门人的组织(比如 ReentrantLock、CountDownLatch、Semaphore),都可以照着这本手册招人、排班、管队列。
AQS 的三大法宝:
- 状态牌(state)
- 一块电子屏,显示“当前金库被占用了几次”。
- 如果是 0,说明没人;如果是 3,说明同一个人进去了 3 次(可重入!)。
- 主排队通道(同步队列 / CLH 队列)
- 所有想进门但没抢到的人,按先后顺序站成一队(双向链表)。
- 队首的人时刻盯着金库门,一旦开了就冲进去。
- 多个等候室(条件队列)
- 不同原因等待的人,去不同的等候室。
- 比如:“等钱到账”的去 A 室,“等老板签字”的去 B 室。
- 当有人喊“钱到了!”,A 室的人就被叫回主排队通道,重新竞争。
- 不同原因等待的人,去不同的等候室。
✅ AQS 的核心思想:不直接管业务,只提供一套通用规则,让各种锁都能高效、安全地工作。
第三章:明星守门人 —— ReentrantLock(可重入锁)
ReentrantLock 是 AQS 手册最著名的毕业生!它比老管程(synchronized)功能更强、更灵活。
它有两大绝技:
💡 绝技 1:可重入
- 同一个人进了金库,发现还要拿另一个箱子,不用出门再排队!
- 直接在内部再拿一次钥匙(state +1),出来时还一次(state -1),直到最后一次才真正关门。
- 避免自己把自己锁在外面!
💡 绝技 2:两种排队策略
- 非公平模式(默认:
新来的人可以“插队”!如果刚好门开了,他能直接冲进去,不管后面有没有人在等。
→ 快!但可能饿死老排队的人。 - 公平模式:
必须严格按先来后到排队,新来的哪怕门开着也得站到队尾。
→ 慢一点,但公平。
它还能干这些 synchronized 干不了的事:
| 功能 | ReentrantLock | synchronized |
|---|---|---|
| 等待可中断 | ✅lockInterruptibly() | ❌ |
| 超时等待 | ✅tryLock(5, SECONDS) | ❌ |
| 多个等候室 | ✅newCondition() | ❌(只有一个) |
| 查看排队人数 | ✅getQueueLength() | ❌ |
就像 ReentrantLock 是个智能保安:能设闹钟、能接电话、能分 VIP 等候区;
而 synchronized 只是个老实门卫:只会开门、关门、喊一声“好了!”。
第四章:加锁和解锁,到底发生了什么?
🔒 加锁过程(lock()):
- 先看状态牌是不是 0(没人)?
- 如果是,直接抢门(CAS 把 state 从 0 改成 1),成功!
- 如果不是,看看是不是自己(可重入)?是就 state+1。
- 如果都不是 → 去主排队通道末尾站好(addWaiter)。
- 然后不断问前面的人:“门开了吗?”(acquireQueued 循环)。
- 一旦轮到你,冲进去!
🔓 解锁过程(unlock()):
- state -1。
- 如果减到 0,说明彻底出来了。
- 通知主排队通道的下一个人:“轮到你了!”(unpark)。
整个过程,没有操作系统介入(轻量级),全靠 AQS 自己管理队列,所以飞快!
第五章:为什么要有这么多设计?
因为现实世界太复杂!
- 90% 的场景:就一个人用金库 → 用偏向锁(连门都不关)。
- 偶尔两人抢:用自旋(在门口转几圈等)。
- 经常多人抢:用 ReentrantLock + 公平队列。
- 需要协作等待:用 Condition 分等候室。
- 只是简单同步:用 synchronized,省心。
JVM 和 JUC 的目标就一个:用最小的代价,解决最大的并发问题。
🎯 最后总结(一句话记住)
• 管程(Monitor) = 老派门卫,简单但死板(synchronized)。
• AQS = 守门人通用培训手册,定义了怎么排队、怎么等、怎么叫人。
• ReentrantLock = AQS 培养出的精英保安,支持插队/公平、多等候室、可打断、可超时,功能强大!
• 它们共同的目标:让多线程安全、高效地共享资源,不多花一分冤枉性能。
下次你写 lock.lock() 的时候,就可以想象:
“我正在召唤一位训练有素的 AQS 保安,他正帮我盯着金库门呢!”








