ReentrantLock是Java并发包中用于实现可重入锁的类,基于AQS(AbstractQueuedSynchronizer,抽象队列同步器)实现,支持公平锁和非公平锁。
- 类结构与AQS基础:ReentrantLock内部有Sync、FairSync和NonFairSync三个内部类。Sync继承自AQS,是公平锁和非公平锁的抽象父类;FairSync和NonFairSync分别实现公平锁和非公平锁。AQS是构建锁和同步器的框架,维护一个FIFO等待队列来管理等待获取资源的线程,通过一个volatile修饰的int类型变量state表示同步状态。不同同步器中,state含义不同,在ReentrantLock中,state表示锁被同一个线程重复获取的次数,0代表未锁定,大于0表示已锁定,数值为获取次数。
- 加锁流程
- 非公平锁加锁:创建ReentrantLock时若不传入参数,默认使用非公平锁(NonFairSync) 。调用lock()方法时,先通过CAS操作尝试将AQS中的同步状态state从0修改为1。若修改成功,将当前线程设置为锁的独占所有者;若失败,调用AQS的acquire(1)方法。acquire(1)方法中,再次调用tryAcquire(1)尝试获取锁(实际调用NonFairSync的nonfairTryAcquire方法 ),该方法先检查state是否为0,是则尝试用CAS将state设为1并设置当前线程为锁的所有者;若state不为0且当前线程是锁的持有者,则将state值增加1,体现可重入特性;若都不满足则返回false。当tryAcquire返回false,执行acquireQueued方法,将当前线程封装成Node节点加入AQS等待队列,然后进入自旋尝试获取锁。若当前节点的前驱节点是头节点且能成功获取锁,则将当前节点设为头节点;否则根据前驱节点的waitStatus值判断是否挂起当前线程。
- 公平锁加锁:公平锁(FairSync)的lock()方法直接调用AQS的acquire(1)方法。在tryAcquire方法中,除了检查state状态,还会调用hasQueuedPredecessors()方法检查等待队列中是否有其他线程在排队。只有等待队列中没有前驱节点(即没有其他线程先于当前线程等待获取锁),并且能通过CAS操作将state从0修改为1时,当前线程才能获取到锁;否则线程会被加入到等待队列。这确保了线程获取锁的顺序是按照在队列中的等待顺序,实现公平性。
- 解锁流程:解锁时调用unlock()方法,实际调用AQS的release(1)方法。release(1)方法内部先调用tryRelease(1)尝试释放锁(在ReentrantLock的Sync类中实现 ),检查当前线程是否是持有锁的线程,若是则将state值减1。当state值变为0时,代表锁已完全释放,将持有锁的线程清空,并返回true。若tryRelease返回true,AQS会调用unparkSuccessor方法唤醒等待队列中的下一个线程(通过LockSupport.unpark()操作 )。
- 可重入的实现:由于ReentrantLock是可重入锁,同一线程可多次获取同一把锁。当已持有锁的线程再次调用lock()方法时,会识别出当前线程是锁的持有者,然后将AQS中的state值加1。释放锁时,每次unlock()操作会将state值减1,直到state值为0时,才真正释放锁。这一机制通过在tryAcquire和tryRelease方法中对state值的增减操作实现,保证了同一线程多次获取锁时不会被阻塞。
- 公平锁与非公平锁区别
- 公平锁:在获取锁时,会检查等待队列中是否有其他线程在排队,只有当前线程是等待队列中第一个请求锁的线程时,才会尝试获取锁,确保了线程获取锁的公平性。但由于需要频繁检查队列,上下文切换开销较大,在高并发场景下性能相对较低。
- 非公平锁:获取锁时先通过CAS尝试获取,不考虑等待队列中的线程。若获取失败,再进入等待队列。这种方式可能会使新线程“插队”获取锁,导致等待队列中的线程等待时间延长,但减少了线程挂起和唤醒的开销,在高并发场景下性能更好。ReentrantLock默认使用非公平锁,因为多数场景下性能更为重要。
170万+

被折叠的 条评论
为什么被折叠?



