共享模式:负数排队,非负通行
独占模式一山不容二虎;共享模式的判定交给 tryAcquireShared,返回值本身就是语言:负数——失败,去排队;零或正数——成功,剩余额度就是返回值。妙在成功之后的传播:setHeadAndPropagate 会在「还有剩余额度、或队列里还有人等」时继续唤醒下一个排队者。Semaphore release 一个许可、队列连环放行;CountDownLatch 最后一个 countDown 把等门闩的线程一波全放完——都是这条传播链在干活。
源码走读:ReentrantLock 非公平锁的 lock()
选它当标本,因为十几行源码就能看懂非公平的本质:
final void lock() {
if (compareAndSetState(0, 1)) // 上来就抢,不管队列有没有人排着
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 抢不到,走 AQS 排队流程
}acquire 展开是三步:tryAcquire 再试一次 → 失败 addWaiter 入队 → acquireQueued 里自旋加 park。tryAcquire 的判定逻辑:state 为 0 就 CAS 抢(公平锁会先调 hasQueuedPredecessors 问一句「队列里有没有人在我前面」,有就不抢);state 非 0 就看持有人是不是自己,是则 state 加一完成重入。公平与非公平的全部差异,就是要不要先看队列——非公平省去唤醒排队线程的开销,吞吐更高,但极端情况下有人饿死;公平人人有份,代价是慢。
挂起唤醒的开关:waitStatus
Node 上的 waitStatus 是队列运转的开关位,四个值各有分工:SIGNAL(-1)——「我马上要 park 了,释放锁时记得叫醒我」,线程入队后先把前驱标成它,才安心挂起;CANCELLED(1)——等锁途中被中断或超时的废票,出队;CONDITION(-2)——排在 Condition 条件队列上的节点(下篇讲);PROPAGATE(-3)——服务共享模式的唤醒传播。理解它,jstack 里 Lock 相关的栈和 AQS 源码就都对上了。
重入的账本
走读下来重入也清楚了:state 就是重入次数的账本——获取时加一,释放时减一,减到零才真正让锁、唤醒后继。所以 unlock 必须和 lock 严格配对:多解一次直接抛 IllegalMonitorStateException,少解一次锁永不释放,后继全体干等。
AQS 的架子和走读都完成了。下一篇从源码抬头看 API:ReentrantLock 比 synchronized 多出来的四样能力——可中断、可超时、可公平、多条件队列——分别怎么用、什么时候用。
评论 (0)