AQS原理+ReentrantLock源码+与synchronized深度对比
并发编程是Java高级开发的核心门槛而AQS、ReentrantLock、synchronized则是并发领域的“铁三角”。很多开发者只会用ReentrantLock和synchronized做同步却不懂其底层依赖的AQS框架面试时被问“ReentrantLock和synchronized的区别”“AQS原理是什么”往往只能答表面无法触及核心。一、前置认知为什么要懂AQSAQSAbstractQueuedSynchronizer即抽象队列同步器是Java并发包java.util.concurrent.locks的核心基石——ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等并发工具底层都依赖AQS实现。简单来说AQS就是一个“模板框架”它定义了一套并发同步的规范封装了“线程排队、锁竞争、唤醒机制”等通用逻辑开发者只需重写少量方法就能快速实现自定义的同步锁或同步器无需重复编写复杂的排队和唤醒逻辑。核心价值解耦“通用并发逻辑”与“具体锁实现”让并发工具的开发更高效、更规范。二、AQS核心原理AQS的核心设计可以概括为一个状态变量 一个双向阻塞队列 一套模板方法三者协同实现线程的同步与竞争。1. 核心组件1状态变量stateAQS用一个volatile修饰的int变量state来表示“同步状态”其含义由具体的实现类如ReentrantLock定义核心作用是标记锁的占用情况和重入次数state 0表示锁处于空闲状态无线程占用state 0表示锁已被占用state的值代表“重入次数”如state2说明当前线程重入了2次state 0无效状态通常不会出现。AQS提供了3个核心方法用于操作state保证原子性和可见性// 获取当前同步状态volatile保证可见性protectedfinalintgetState(){returnstate;}// 设置同步状态仅在当前线程持有锁时使用无并发竞争protectedfinalvoidsetState(intnewState){statenewState;}// CAS原子更新同步状态核心用于锁竞争时的状态修改protectedfinalbooleancompareAndSetState(intexpect,intupdate){returnunsafe.compareAndSwapInt(this,stateOffset,expect,update);}注unsafe是Java底层的Unsafe类提供了CAS原子操作保证state的修改是线程安全的stateOffset是state变量在AQS类中的内存偏移量用于Unsafe直接操作内存。2. 核心组件2双向阻塞队列CLH队列当多个线程竞争锁失败时AQS会将这些线程封装成“节点Node”加入到一个双向虚拟队列中CLH队列变体让线程阻塞等待当锁释放时再从队列中唤醒一个线程竞争锁。CLH队列的核心特点虚拟队列队列本身没有实际的容器而是通过节点的prev前驱和next后继指针形成双向链表结构节点状态每个Node都有一个waitStatus变量标记节点的状态如等待中、已取消、已唤醒用于控制线程的阻塞和唤醒FIFO原则队列遵循“先进先出”保证线程竞争锁的顺序公平锁依赖此特性非公平锁可打破此顺序。Node节点核心结构staticfinalclassNode{// 标记节点类型独占锁ReentrantLock用此类型、共享锁Semaphore用此类型staticfinalNodeEXCLUSIVEnull;staticfinalNodeSHAREDnewNode();// 节点状态CANCELLED1已取消、SIGNAL-1等待唤醒、CONDITION-2条件等待等volatileintwaitStatus;// 前驱节点volatileNodeprev;// 后继节点volatileNodenext;// 当前节点对应的线程volatileThreadthread;// 条件队列相关ReentrantLock的Condition依赖NodenextWaiter;}队列工作流程线程竞争锁失败 → 封装成Node节点通过CAS操作将节点加入队列尾部当前线程调用LockSupport.park()方法进入阻塞状态持有锁的线程释放锁时唤醒队列头部的节点该节点对应的线程重新竞争锁。3. 核心组件3模板方法模式AQS的核心设计模式是模板方法模式它定义了一套“锁获取、锁释放”的通用流程模板方法将具体的“锁竞争、锁释放”逻辑交给子类如ReentrantLock的Sync内部类重写。AQS的核心模板方法开发者无需重写直接调用acquire(int arg)独占式获取锁ReentrantLock的lock()方法底层调用此方法release(int arg)独占式释放锁ReentrantLock的unlock()方法底层调用此方法acquireShared(int arg)共享式获取锁Semaphore的acquire()方法底层调用releaseShared(int arg)共享式释放锁Semaphore的release()方法底层调用。AQS要求子类重写的核心方法仅需重写这几个其余逻辑由AQS封装tryAcquire(int arg)独占式尝试获取锁ReentrantLock的公平/非公平锁核心实现tryRelease(int arg)独占式尝试释放锁tryAcquireShared(int arg)共享式尝试获取锁tryReleaseShared(int arg)共享式尝试释放锁isHeldExclusively()判断当前线程是否独占持有锁用于可重入性判断。模板方法流程以acquire()为例// AQS的acquire方法模板方法定义了独占式获取锁的完整流程publicfinalvoidacquire(intarg){// 1. 尝试获取锁子类重写tryAcquire获取成功则直接返回if(!tryAcquire(arg)// 2. 尝试获取锁失败将当前线程封装成Node加入队列acquireQueued(addWaiter(Node.EXCLUSIVE),arg)){// 3. 如果线程在队列中被中断标记当前线程为中断状态selfInterrupt();}}总结AQS核心用state控制锁的状态用CLH队列管理等待线程用模板方法封装通用流程子类重写具体的锁逻辑——这就是ReentrantLock能够实现公平锁、非公平锁、可重入性的底层基础。三、ReentrantLock源码解析基于AQS的实现ReentrantLock可重入锁是AQS最经典的实现类核心特性可重入、支持公平锁和非公平锁默认非公平锁、支持中断、支持条件等待Condition。ReentrantLock的核心结构内部维护了一个Sync抽象内部类继承AQSSync有两个子类——NonfairSync非公平锁和FairSync公平锁ReentrantLock的所有锁操作最终都委托给Sync子类实现。1. ReentrantLock构造方法公平/非公平锁选择// 无参构造默认非公平锁性能更优大多数场景首选publicReentrantLock(){syncnewNonfairSync();}// 有参构造true公平锁false非公平锁publicReentrantLock(booleanfair){syncfair?newFairSync():newNonfairSync();}公平锁与非公平锁的核心区别公平锁严格遵循“先到先得”CLH队列顺序非公平锁允许“插队”新线程可直接竞争锁无需排队。非公平锁性能更优因为减少了线程排队、唤醒的开销公平锁则保证了线程竞争的公平性避免线程饥饿。2. 核心方法lock()获取锁ReentrantLock的lock()方法本质是调用Sync子类的lock()方法再间接调用AQS的acquire(1)方法arg1表示每次获取锁state1。1非公平锁NonfairSynclock()staticfinalclassNonfairSyncextendsSync{privatestaticfinallongserialVersionUID7316153563782823691L;// 非公平锁获取锁的核心方法finalvoidlock(){// 1. 尝试CAS抢锁state从0→1如果成功标记当前线程为锁持有者if(compareAndSetState(0,1))setExclusiveOwnerThread(Thread.currentThread());else// 2. 抢锁失败调用AQS的acquire(1)进入队列等待acquire(1);}// 重写AQS的tryAcquire方法非公平锁的锁竞争逻辑protectedfinalbooleantryAcquire(intacquires){returnnonfairTryAcquire(acquires);}}核心逻辑非公平锁的“插队”特性体现在第一步——即使队列中有等待的线程新线程也会先尝试CAS抢锁抢锁成功则直接持有锁无需排队只有抢锁失败才会加入队列。2nonfairTryAcquire()非公平锁尝试获取锁// 非公平锁尝试获取锁的核心逻辑可重入性实现finalbooleannonfairTryAcquire(intacquires){finalThreadcurrentThread.currentThread();intcgetState();// 1. 如果锁空闲state0直接CAS抢锁if(c0){if(compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}// 2. 如果当前线程就是锁持有者可重入性state1elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)// 防止重入次数溢出thrownewError(Maximum lock count exceeded);setState(nextc);// 无需CAS因为当前线程已持有锁无并发竞争returntrue;}// 3. 锁被其他线程持有抢锁失败returnfalse;}3公平锁FairSynclock()staticfinalclassFairSyncextendsSync{privatestaticfinallongserialVersionUID-3000897897090466540L;finalvoidlock(){// 公平锁不“插队”直接调用AQS的acquire(1)进入队列排队acquire(1);}// 重写AQS的tryAcquire方法公平锁的锁竞争逻辑protectedfinalbooleantryAcquire(intacquires){finalThreadcurrentThread.currentThread();intcgetState();if(c0){// 关键区别公平锁抢锁前先判断队列中是否有等待的线程// hasQueuedPredecessors()判断当前线程是否是队列的第一个节点if(!hasQueuedPredecessors()compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}// 可重入性逻辑与非公平锁一致elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)thrownewError(Maximum lock count exceeded);setState(nextc);returntrue;}returnfalse;}}核心区别公平锁在抢锁前会通过hasQueuedPredecessors()方法判断“队列中是否有比当前线程更早等待的线程”如果有当前线程不会抢锁直接加入队列只有队列中没有等待线程才会尝试CAS抢锁——这就是公平锁“先到先得”的核心实现。3. 核心方法unlock()释放锁ReentrantLock的unlock()方法无论公平锁还是非公平锁逻辑一致都是调用Sync的release(1)方法再间接调用AQS的release(1)方法。// ReentrantLock的unlock方法publicvoidunlock(){sync.release(1);}// Sync类继承AQS的release方法protectedfinalbooleantryRelease(intreleases){// 1. 重入次数减1releases1intcgetState()-releases;// 2. 只有锁持有者才能释放锁否则抛出异常if(Thread.currentThread()!getExclusiveOwnerThread())thrownewIllegalMonitorStateException();booleanfreefalse;// 3. 如果重入次数为0说明锁已完全释放标记锁为空闲if(c0){freetrue;setExclusiveOwnerThread(null);// 清空锁持有者}// 4. 更新state即使未完全释放也要更新重入次数setState(c);returnfree;}核心逻辑可重入锁的释放是“渐进式”的——每次unlock()state减1只有当state减到0时才会真正释放锁清空锁持有者并唤醒队列中的等待线程如果state0说明线程还在重入锁未完全释放。注意ReentrantLock的unlock()必须手动调用且必须放在finally块中否则会导致锁泄漏线程异常退出锁未释放其他线程无法获取锁。4. 关键特性可重入性、中断、Condition可重入性通过tryAcquire()中“判断当前线程是否是锁持有者”实现state记录重入次数每次获取锁state1释放锁state-1state0时锁释放可中断ReentrantLock提供lockInterruptibly()方法允许线程在等待锁时被中断synchronized不支持中断Condition条件等待通过newCondition()方法创建Condition对象实现“等待-通知”机制支持多条件等待synchronized只能通过wait()/notify()实现单一条件等待。四、ReentrantLock与synchronized深度对比ReentrantLock和synchronized都是Java中实现线程同步的核心方式二者底层原理不同、特性不同适用场景也不同。1. 核心对比表对比维度ReentrantLocksynchronized底层实现基于AQS框架Java代码实现依赖CAS和CLH队列JVM内置锁C实现基于对象头的Monitor管程模型锁类型可重入锁支持公平锁、非公平锁默认非公平可重入锁仅支持非公平锁JDK1.8优化后性能接近ReentrantLock锁释放手动释放必须在finally块中调用unlock()否则锁泄漏自动释放线程退出同步块/方法时JVM自动释放锁无需手动操作中断支持支持中断lockInterruptibly()方法等待锁的线程可被中断不支持中断等待锁的线程会一直阻塞无法被中断条件等待支持多条件等待Condition可实现精准的线程唤醒仅支持单一条件等待wait()/notify()/notifyAll()唤醒时随机唤醒一个线程锁粒度细粒度可灵活控制锁的范围如代码块锁粗粒度早期JDK1.8后优化支持偏向锁、轻量级锁粒度可细化性能高并发场景下性能更优无锁竞争时略逊于synchronized有CAS开销JDK1.8前性能较差JDK1.8后偏向锁、轻量级锁性能接近ReentrantLock无锁竞争时性能更优公平性可选择公平/非公平锁公平锁保证线程排队顺序仅非公平锁无法保证线程竞争顺序可能出现线程饥饿使用复杂度较复杂需手动创建锁对象、手动释放注意异常处理简单只需加关键字方法/代码块无需手动管理锁的生命周期适用场景高并发、需要灵活控制锁中断、公平锁、多条件等待的场景低并发、简单同步场景或不需要灵活控制锁的场景优先选择开发效率高2. 关键差异补充1锁的升级机制不同synchronized在JDK1.8后引入了“锁升级”机制无锁 → 偏向锁 → 轻量级锁 → 重量级锁根据线程竞争情况动态升级锁的级别减少锁的开销而ReentrantLock没有锁升级机制始终基于AQS的CLH队列和CAS操作锁的级别固定独占锁。2异常处理不同synchronized遇到异常时JVM会自动释放锁不会导致锁泄漏而ReentrantLock如果在lock()后、unlock()前发生异常会导致锁无法释放锁泄漏因此必须将unlock()放在finally块中确保无论是否异常锁都能释放。3线程唤醒机制不同synchronized的notify()方法会随机唤醒一个等待的线程notifyAll()会唤醒所有等待的线程无法精准唤醒指定线程而ReentrantLock的Condition可以实现精准唤醒signal()唤醒一个指定条件的线程signalAll()唤醒所有指定条件的线程灵活性更高。3. 实战选择建议如果是简单的同步场景如单例模式、简单方法同步优先选择synchronized——开发效率高无需手动管理锁JDK优化后性能足够如果是高并发场景或需要灵活控制锁如公平锁、中断、多条件等待选择ReentrantLock——灵活性高性能更稳定如果是分布式场景无论是ReentrantLock还是synchronized都不适用仅支持单机同步需使用分布式锁如Redisson、ZooKeeper实现。五、总结核心要点梳理AQS核心一个state状态变量控制锁状态、一个CLH双向队列管理等待线程、一套模板方法封装通用流程是并发工具的底层基石ReentrantLock核心基于AQS实现内部有NonfairSync非公平锁和FairSync公平锁两个子类支持可重入、中断、多条件等待需手动释放锁与synchronized差异底层实现不同、锁类型不同、释放方式不同、灵活性不同选择时需结合场景简单场景用synchronized复杂场景用ReentrantLock重点AQS的核心组件、ReentrantLock的公平/非公平锁实现、可重入性原理、二者的核心差异及适用场景。懂AQS才能看懂Java并发工具的底层逻辑懂ReentrantLock与synchronized的差异才能在生产中选择最合适的同步方式。并发编程的核心不是“加锁”而是“合理控制线程竞争”——AQS提供了竞争的框架ReentrantLock和synchronized提供了具体的竞争实现掌握它们才能搞定高并发场景。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2552196.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!