欢迎光临
我们一直在努力

《当 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 干不了的事:

    功能ReentrantLocksynchronized
    等待可中断 ✅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 保安,他正帮我盯着金库门呢!” 看懂故事之后再看正文 – 深入理解AQS之独占锁ReentrantLock源码分析

    赞(0)
    未经允许不得转载:171主机测评 » 《当 synchronized 遇上 ReentrantLock:一场守门人的革命》
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址