AQS 与 ReentrantLock:队列同步器与可重入锁

发布时间:2026/6/4 3:58:02

AQS 与 ReentrantLock:队列同步器与可重入锁 如果说synchronized是 JVM 给你的内置锁那 AQS 就是 JUC 里很多同步工具的地基。ReentrantLock、Semaphore、CountDownLatch这些类看起来用法不同但底层都有一套相似的骨架一个 state 表示资源状态一条 FIFO 队列保存等待线程再用 CAS 保证抢资源时的原子性。这套骨架就是 AQS。AQS 是什么AQS全称AbstractQueuedSynchronizer抽象队列同步器。它不是一个直接拿来用的业务工具而是用来构建锁和同步组件的基础框架。常见基于 AQS 的组件组件用途ReentrantLock可重入互斥锁Semaphore信号量控制并发访问数量CountDownLatch倒计时锁等待多个任务完成ReentrantReadWriteLock读写锁AQS 的核心结构AQS 最核心的东西有两个volatile int state。一个 FIFO 双向队列。state表示同步状态。不同工具对它的解释不同。在ReentrantLock里state 0通常表示没有线程持锁state 0表示锁被持有并且数值可以表示重入次数。在Semaphore里state可以表示剩余许可数。在CountDownLatch里state可以表示还没倒完的计数。AQSvolatile int stateFIFO 双向等待队列headNode: Thread-1Node: Thread-2tailCAS 修改资源状态这就是 AQS 的漂亮之处它把“抢资源、失败排队、唤醒后继”这些通用流程抽出来让不同同步工具只需要定义怎么获取和释放资源。抢锁过程怎么走以互斥锁为例线程来抢资源时大概这么走是是否否线程尝试获取资源state 是否可获取CAS 修改 stateCAS 是否成功获取成功封装成 Node 入队在 AQS 队列中等待前驱释放资源后被唤醒这里的关键点是 CAS。多个线程同时看到state 0时只有一个线程能 CAS 成功。其他线程 CAS 失败后就会进入队列等待。AQS 公平还是非公平AQS 本身既能支持公平也能支持非公平。具体公平不公平看上层同步器怎么实现。公平锁的思路是新来的线程先看队列里有没有人排队。如果有人排队就不要插队去队尾等。非公平锁的思路是新来的线程可以先尝试抢一下。抢到了就直接执行抢不到再排队。是有没有否是否新线程到来公平锁?队列里是否有等待线程进入队列排队尝试 CAS 抢锁抢锁成功?执行临界区非公平锁吞吐量通常更高因为减少了严格排队带来的调度成本。但它可能让队列里的老线程等得更久。ReentrantLock 有哪些能力ReentrantLock是 JUC 里的可重入锁。它和synchronized一样能互斥、能重入但提供了更多控制能力能力synchronizedReentrantLock自动释放锁是否需要unlock()可重入是是公平锁不支持显式选择构造器可选择可中断等待不方便lockInterruptibly()超时获取锁不支持tryLock(timeout, unit)多条件队列一个 WaitSet多个Condition基本用法一定要写成try finallyReentrantLocklocknewReentrantLock();lock.lock();try{// 临界区}finally{lock.unlock();}ReentrantLock不会像synchronized那样自动释放锁。如果忘了unlock()后面的线程可能永远等下去。公平锁和非公平锁ReentrantLock默认是非公平锁ReentrantLocklocknewReentrantLock();也可以传true创建公平锁ReentrantLockfairLocknewReentrantLock(true);内部大致是选择不同的 Sync 实现publicReentrantLock(){syncnewNonfairSync();}publicReentrantLock(booleanfair){syncfair?newFairSync():newNonfairSync();}非公平锁不是“乱来”它只是允许新线程先尝试 CAS 抢锁。如果失败还是会进入 AQS 队列。可中断和可超时synchronized的一个限制是线程如果阻塞在获取锁上不太方便被取消。ReentrantLock可以用lockInterruptibly()try{lock.lockInterruptibly();}catch(InterruptedExceptione){Thread.currentThread().interrupt();return;}try{// 临界区}finally{lock.unlock();}也可以用tryLock()设置超时时间if(!lock.tryLock(2,TimeUnit.SECONDS)){System.out.println(获取锁失败);return;}try{// 临界区}finally{lock.unlock();}这在业务系统里很有用。比如一个操作拿不到锁就快速失败不要一直把请求线程挂死。多条件变量synchronized每个对象只有一个 WaitSet调用notify()或notifyAll()时很难精准唤醒某一类等待线程。ReentrantLock可以创建多个ConditionstaticReentrantLocklocknewReentrantLock();staticConditionc1lock.newCondition();staticConditionc2lock.newCondition();不同条件的线程进入不同等待队列需要时可以精准唤醒。ReentrantLockAQS 同步队列Condition c1 等待队列Condition c2 等待队列c1.signal / signalAllc2.signal / signalAll注意await()、signal()这些方法也必须在持有锁的情况下调用。synchronized 和 Lock 怎么比较可以从三个层面说。语法层面synchronized是关键字JVM 层面实现退出同步代码块自动释放锁。Lock是接口JDK 层面实现需要手动释放锁。功能层面二者都能互斥、同步、重入。Lock额外支持公平锁、可中断、可超时、多条件变量等能力。性能层面早期synchronized性能较弱但后来有偏向锁、轻量级锁等优化低竞争场景并不差。竞争复杂、需要更多控制能力时ReentrantLock更灵活。面试怎么答可以这样讲AQS 是 JUC 里构建锁和同步工具的基础框架核心是一个volatile int state和一条 FIFO 双向队列。线程获取资源时会先用 CAS 修改 state成功就获取资源失败就封装成 Node 进入队列等待。ReentrantLock底层就是基于 AQS 实现的。它默认是非公平锁也可以通过构造器创建公平锁。非公平锁允许新来的线程先 CAS 抢一下锁公平锁会先判断队列里是否已有等待线程。相比synchronizedReentrantLock支持可中断、可超时、公平锁、多条件变量但必须在finally里手动unlock()。如果只是简单互斥synchronized更简洁如果需要更强控制能力用ReentrantLock更合适。

相关新闻