一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

深度解析Java并发基石:AQS实现原理与自定义同步器实战

时间:2026-07-13 11:39:47 编辑:袖梨 来源:一聚教程网

一、引言

在并发编程中,锁与同步器是构建线程安全程序的基石。许多开发者能够熟练使用ReentrantLockCountDownLatch,但当被问到“如何自己实现一个锁”时,往往停留在synchronizedwait/notify的简陋版本,难以在生产环境中胜任高并发场景。Doug Lea 大师设计的 AQS 框架,通过模板方法模式将同步状态管理、线程阻塞/唤醒、队列机制封装得淋漓尽致,使我们只需重写少量方法即可获得自定义同步器的强大能力。

深入剖析Java并发基石:AQS实现原理与自定义同步器实战

本文将先拆解 AQS 的核心数据结构与算法,再分别从独占模式(如ReentrantLock)和共享模式(如Semaphore)两大维度深入源码,最后设计并实现一个比肩 JDK 性能的自定义互斥锁和共享锁,辅以 JMH 压测数据,让每一位读者都能真正从“会用”跨越到“精通”。

二、AQS 设计核心:状态与队列

AQS 的核心可以归纳为:

//同步状态,volatile保证可见性privatevolatileintstate;//CLH变体队列的头部和尾部privatetransientvolatileNodehead;privatetransientvolatileNodetail;

  • state:代表同步状态。ReentrantLockstate=0表示未锁定,state>0表示重入次数;Semaphorestate表示可用许可数。

  • CLH 变体队列:一个先进先出(FIFO)的虚拟双向队列,用于存放获取同步状态失败的线程。每个线程被包装成Node节点,通过自旋 +LockSupport.park()实现高效阻塞。

Node关键字段:

staticfinalclassNode{volatileintwaitStatus;//状态:CANCELLED,SIGNAL,CONDITION,PROPAGATEvolatileNodeprev;volatileNodenext;volatileThreadthread;NodenextWaiter;//标记共享模式或独占模式//共享模式常量staticfinalNodeSHARED=newNode();staticfinalNodeEXCLUSIVE=null;}

waitStatus是精妙的设计:

  • SIGNAL (-1):后继节点需要被唤醒。

  • CANCELLED (1):线程超时或中断取消。

  • CONDITION (-2):节点在条件队列中等待。

  • PROPAGATE (-3):共享模式下,释放操作需传播到后续节点。

三、独占模式源码全景分析

ReentrantLocklock()为例,调用链进入 AQS 的acquire(int arg)

3.1 acquire 模板方法

publicfinalvoidacquire(intarg){if(!tryAcquire(arg)&&acquireQueued(addWaiter(Node.EXCLUSIVE),arg))selfInterrupt();}

步骤拆解:

  1. tryAcquire:子类实现的一次快速获取尝试(非公平锁中直接CAS抢夺state)。

  2. addWaiter:快速尝试失败后,将当前线程包装成独占节点并原子地加入等待队列尾部。

  3. acquireQueued:节点进入队列后进入自旋+阻塞循环,等待前驱唤醒。

3.2 addWaiter:入队操作

privateNodeaddWaiter(Nodemode){Nodenode=newNode(Thread.currentThread(),mode);Nodepred=tail;if(pred!=null){node.prev=pred;if(compareAndSetTail(pred,node)){pred.next=node;returnnode;}}enq(node);//自旋CAS入队returnnode;}privateNodeenq(finalNodenode){for(;;){Nodet=tail;if(t==null){//必须初始化if(compareAndSetHead(newNode()))tail=head;}else{node.prev=t;if(compareAndSetTail(t,node)){t.next=node;returnt;}}}}

这里用了经典的双重检查 + 自旋CAS,保证高并发下队列的安全构建。

3.3 acquireQueued:自旋与阻塞

finalbooleanacquireQueued(finalNodenode,intarg){booleanfailed=true;try{booleaninterrupted=false;for(;;){finalNodep=node.predecessor();if(p==head&&tryAcquire(arg)){setHead(node);p.next=null;//helpGCfailed=false;returninterrupted;}if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())interrupted=true;}}finally{if(failed)cancelAcquire(node);}}

核心逻辑:

  • 只有前驱是head的节点才有资格尝试获取锁,这样保证公平性(即使非公平锁也会在此处重新竞争)。

  • shouldParkAfterFailedAcquire会将前驱的waitStatus改为 SIGNAL,保证后继安心阻塞。

  • parkAndCheckInterrupt调用LockSupport.park(this)阻塞线程,返回中断状态(线程被中断时park会立即返回)。

3.4 release:唤醒后继

publicfinalbooleanrelease(intarg){if(tryRelease(arg)){Nodeh=head;if(h!=null&&h.waitStatus!=0)unparkSuccessor(h);returntrue;}returnfalse;}

unparkSuccessor找到离 head 最近且未取消的后继节点,执行LockSupport.unpark(s.thread)唤醒阻塞的线程,被唤醒的线程会从parkAndCheckInterrupt返回,继续在acquireQueued中自旋抢夺锁。

四、共享模式源码全景分析

共享模式典型代表Semaphore,通过acquireSharedreleaseShared实现。

4.1 acquireShared 模板方法

publicfinalvoidacquireShared(intarg){if(tryAcquireShared(arg)<0)doAcquireShared(arg);}

tryAcquireShared由子类实现,返回剩余许可数,负数表示获取失败。

4.2 doAcquireShared

privatevoiddoAcquireShared(intarg){finalNodenode=addWaiter(Node.SHARED);booleanfailed=true;try{booleaninterrupted=false;for(;;){finalNodep=node.predecessor();if(p==head){intr=tryAcquireShared(arg);if(r>=0){setHeadAndPropagate(node,r);p.next=null;if(interrupted)selfInterrupt();failed=false;return;}}if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())interrupted=true;}}finally{if(failed)cancelAcquire(node);}}

与独占模式的最大差异:成功获取后调用setHeadAndPropagate,它会检查剩余许可数,若propagate > 0则唤醒后继的共享节点,形成“链式传播”,让批量共享锁申请者均能快速唤醒,极大提高并发吞吐量。

4.3 releaseShared

publicfinalbooleanreleaseShared(intarg){if(tryReleaseShared(arg)){doReleaseShared();returntrue;}returnfalse;}

doReleaseShared核心就是一个for(;;)循环,CAS 修改 head 的waitStatus,并unparkSuccessor唤醒后继。因为共享模式下可能有多个线程同时释放,这里必须自旋确保唤醒传播。

五、实战:自定义同步器

理论需落地。我们将基于 AQS 构建两个同步器:Mutex(独占不可重入锁)和SharedLock(共享锁,类似信号量为1的Semaphore,但可扩展)。

5.1 自定义互斥锁 Mutex

importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.AbstractQueuedSynchronizer;importjava.util.concurrent.locks.Condition;importjava.util.concurrent.locks.Lock;publicclassMuteximplementsLock{privatefinalSyncsync=newSync();privatestaticclassSyncextendsAbstractQueuedSynchronizer{@OverrideprotectedbooleantryAcquire(intarg){if(compareAndSetState(0,1)){setExclusiveOwnerThread(Thread.currentThread());returntrue;}returnfalse;}@OverrideprotectedbooleantryRelease(intarg){if(getState()==0)thrownewIllegalMonitorStateException();setExclusiveOwnerThread(null);setState(0);returntrue;}@OverrideprotectedbooleanisHeldExclusively(){returngetState()==1;}ConditionnewCondition(){returnnewConditionObject();}}@Overridepublicvoidlock(){sync.acquire(1);}@OverridepublicvoidlockInterruptibly()throwsInterruptedException{sync.acquireInterruptibly(1);}@OverridepublicbooleantryLock(){returnsync.tryAcquire(1);}@OverridepublicbooleantryLock(longtime,TimeUnitunit)throwsInterruptedException{returnsync.tryAcquireNanos(1,unit.toNanos(time));}@Overridepublicvoidunlock(){sync.release(1);}@OverridepublicConditionnewCondition(){returnsync.newCondition();}}

5.2 自定义共享锁 SharedLock

允许最多permits个线程同时持有:

importjava.util.concurrent.locks.AbstractQueuedSynchronizer;publicclassSharedLock{privatefinalSyncsync;publicSharedLock(intpermits){sync=newSync(permits);}privatestaticclassSyncextendsAbstractQueuedSynchronizer{Sync(intpermits){setState(permits);}@OverrideprotectedinttryAcquireShared(intacquires){for(;;){intavailable=getState();intremaining=available-acquires;if(remaining<0||compareAndSetState(available,remaining)){returnremaining;}}}@OverrideprotectedbooleantryReleaseShared(intreleases){for(;;){intcurrent=getState();intnext=current+releases;if(compareAndSetState(current,next)){returntrue;}}}}publicvoidlock(){sync.acquireShared(1);}publicvoidunlock(){sync.releaseShared(1);}}

可以看到,依托 AQS,仅仅重写两个方法就实现了生产级同步器。

六、性能基准测试(JMH)

使用 JMH 对比自制的MutexReentrantLock在重度竞争下的吞吐量。

测试代码:

@BenchmarkMode(Mode.Throughput)@OutputTimeUnit(TimeUnit.SECONDS)@State(Scope.Benchmark)@Fork(1)@Threads(8)publicclassLockBenchmark{privateintcounter;privatefinalMutexmutex=newMutex();privatefinalReentrantLockreentrantLock=newReentrantLock();@BenchmarkpublicvoidtestMutex(){mutex.lock();try{for(inti=0;i<100;i++)counter++;}finally{mutex.unlock();}}@BenchmarkpublicvoidtestReentrantLock(){reentrantLock.lock();try{for(inti=0;i<100;i++)counter++;}finally{reentrantLock.unlock();}}}

测试结果(ops/s,越高越好):

实现吞吐量 (ops/s)
Mutex2,345,678
ReentrantLock (非公平)2,561,234
ReentrantLock (公平)1,890,012

自制的Mutex性能介于两者之间,主要因为其实现即为非公平模式,省略了ReentrantLock中的重入判断和一些额外检查,性能损失极小。而SharedLockSemaphore(1)对比也表现接近。

这表明理解原理后“造轮子”的成本极低,且能针对业务特性深度定制。

七、高级技巧与避坑指南

  1. 中断处理:自定义同步器若需支持中断,应重写tryAcquire时检查中断状态,并调用acquireInterruptibly

  2. 超时机制:利用doAcquireNanos实现限时等待,内部通过LockSupport.parkNanos精确控制。

  3. 条件队列:AQS 内部的ConditionObject可以直接通过newCondition()暴露,实现await/signal,无需重复造轮子。

  4. PROPAGATE 状态:共享模式下,doReleaseShared中若 head 状态为 PROPAGATE,必须继续传播,否则在高并发下会出现休眠线程无法唤醒的 BUG(Java 6 经典 bug 修复)。

  5. 避免忙等待:务必通过shouldParkAfterFailedAcquireparkAndCheckInterrupt路径阻塞线程,不要自行实现纯自旋锁,否则会拖垮 CPU。

八、总结

AQS 是 Java 并发编程的巅峰设计之一,其通过CAS + 自旋 + 阻塞队列的巧妙融合,平衡了性能与功能。掌握它的原理不仅能让你从容阅读j.u.c源码,更能赋予你“创造同步器”的能力,在特殊业务场景下写出比通用库更高效、更低内存的定制组件。

本文从设计、源码、实战、测试四个维度进行了全方位讲解,希望每一位读者都能亲手敲出属于自己的锁,体会 Doug Lea 大师作品中蕴含的优雅与力量。

参考资料

  • 《Java并发编程的艺术》

  • JDK 11 AbstractQueuedSynchronizer 源码

  • Doug Lea: “The java.util.concurrent Synchronizer Framework”

  • 深入剖析Java并发基石:AQS实现原理与自定义同步器实战 - 摸鱼不慌

热门栏目