尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ReentrantLock 到底怎么排队的?AQS 源码拆解

ReentrantLock 到底怎么排队的?AQS 源码拆解 synchronized用得好好的为什么要用 ReentrantLock可中断、可超时、公平锁、多条件变量这些名词背得滚瓜烂熟但一问底层怎么实现的就只剩基于 AQS五个字。这篇把 ReentrantLock 和它背后的 AQSAbstractQueuedSynchronizer从源码层面拆开看完你就知道锁到底是怎么排队的、公平和非公平差在哪、Condition 是怎么做到精准唤醒的。一、AQS 是什么一把锁的核心三件套AQS 本质上是一个同步器框架核心只有三样东西// 1. 状态位volatile int state0没锁0被持锁privatevolatileintstate;// 2. CLH 变体双向队列排队等锁的线程staticfinalclassNode{volatileThreadthread;// 排队的线程volatileNodeprev;// 前驱volatileNodenext;// 后继volatileintwaitStatus;// 节点状态}// head 指向队首持有锁或即将持有tail 指向队尾privatetransientvolatileNodehead;privatetransientvolatileNodetail;一句话state 记录锁被占了几次队列记录谁在等。所有同步器锁、信号量、栅栏都复用这套骨架。二、公平锁 vs 非公平锁只差一行ReentrantLock 默认非公平。看源码// 非公平锁加锁finalvoidlock(){if(compareAndSetState(0,1))// 来了就抢不管队列里有没有人等setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}// 公平锁加锁finalvoidlock(){acquire(1);}// acquire → tryAcquireprotectedfinalbooleantryAcquire(intacquires){finalThreadcurrentThread.currentThread();intcgetState();if(c0){// 公平锁必须队列为空才抢非公平锁直接抢if(!hasQueuedPredecessors()compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}...}非公平新线程来先 CAS 抢一次抢不到才排队公平先看队列有没有人等有人就老实排队。所以非公平吞吐高减少上下文切换但可能插队饥饿风险小但长期排队者可能晚一点拿到。三、获取锁失败怎么办acquire 全流程publicfinalvoidacquire(intarg){if(!tryAcquire(arg)// 1. 尝试获取公平/非公平差异在这acquireQueued(addWaiter(Node.EXCLUSIVE),arg))// 2. 失败→入队→排队自旋Thread.currentThread().interrupt();}拆解第一步入队addWaiterprivateNodeaddWaiter(Nodemode){NodenodenewNode(mode);// 包装当前线程Nodepredtail;if(pred!null){node.prevpred;if(compareAndSetTail(pred,node)){// CAS 挂到队尾pred.nextnode;returnnode;}}enq(node);// 队列为空或 CAS 失败用自旋入队returnnode;}第二步排队等待acquireQueuedfinalbooleanacquireQueued(finalNodenode,intarg){booleaninterruptedfalse;for(;;){finalNodepnode.predecessor();if(pheadtryAcquire(arg)){// 前驱是队首 → 再抢一次setHead(node);// 拿到锁自己变队首p.nextnull;// 出队returninterrupted;}// 没拿到 → 挂起线程park等前驱唤醒if(shouldParkAfterFailedAcquire(p,node))interrupted|parkAndCheckInterrupt();}}核心每个节点在自己的前驱是 head时才有资格尝试获取锁——这就是队列的 FIFO 特性非公平也只在入队前插队一次入队后就老实排队。四、释放锁唤醒后继publicfinalbooleanrelease(intarg){if(tryRelease(arg)){// state 减到 0 才算释放Nodehhead;if(h!null){LockSupport.unpark(h.next.thread);// 唤醒队首的下一个}returntrue;}returnfalse;}关键tryRelease里 state 减到 0 才真正释放可重入时 state 0 只是减一锁还在。唤醒的永远是head 的下一个节点。五、可重入state 是怎么玩出花的// 可重入同一线程再次 lockprotectedfinalbooleantryAcquire(intacquires){ThreadcurrentThread.currentThread();intcgetState();if(c0){...}// 无人持有 → 直接拿elseif(currentgetExclusiveOwnerThread()){// 自己已持有intnextccacquires;// state 1setState(nextc);// 重入1returntrue;}returnfalse;// 别人持有 → 失败}state 就是重入计数0无锁1持有一层2同一线程重入两次……每次 unlock 减一减到 0 才真正释放唤醒后继。六、Condition精准唤醒怎么实现synchronized只有 wait/notify 一个等待集ReentrantLock 可以 new 多个 Condition每个 Condition 一条独立等待队列。LocklocknewReentrantLock();ConditionnotFulllock.newCondition();ConditionnotEmptylock.newCondition();condition.await()当前线程释放锁加入该 condition 的等待队列挂起condition.signal()把等待队列队首一个节点移到锁的同步队列等它重新竞争signalAll()全部移到同步队列对比 synchronized能力synchronizedReentrantLock Condition锁释放自动是否finally unlock可重入是是中断响应否是超时等待否tryLock(3, SECONDS)条件变量1 个多个 Condition公平性非公平可配置七、总结AQS 三件套state 双向等待队列 CAS是所有同步工具的地基。公平/非公平公平 入队前看队列非公平 先 CAS 抢一次。入队后都 FIFO。获取流程tryAcquire → 入队 → 自旋检查前驱是 head → park。释放流程state 减到 0 → unpark 后继。重入state 就是重入计数减到 0 才真正释放。Condition每个 Condition 一条等待队列精准唤醒。下一篇synchronized 和 volatile 的底层原理JMM 三大特性是怎么落到 CPU 缓存上的。
返回列表