Java中AbstractQueuedSynchronizer原理浅析

1 什么是AQS

AQS,即AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基础框架。在JSR166(java规范提案)中提出并创建。有以下特点:

  • 用int state 属性来表示资源的状态,子类需要定义如何维护这个状态,控制获取锁和释放锁;
  • 提供了基于 FIFO 的等待队列,类似 Monitor的 EntryList;
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor的 WaitSet。

AQS是一个抽象类,主要通过继承来使用,子类实现它的抽象方法来管理同步状态。AQS自身没有实现任何同步接口,仅是定义了若干同步状态获取和释放的方法。ReentrantLock、ReentrantReadWriteLock和CountDownLatch等,都是基于它实现的。

2 两种模式

在使用中,AQS有两种模式:独占式和共享式

  • 独占式,就是在同一时刻最多只有一个线程获取到锁,而其他获取锁的线程只能 处于同步队列中等待,直到获取锁的线程释放了锁。ReentrantLock就是独占式锁;
  • 共享式,允许多个线程同时获取到锁,并发访问共享资源,比如ReentrantReadWriteLock。

3 要点

  • 原子维护 state 状态:使用 volatile 配合 cas 保证其修改时的原子性、可见性、有序性 
  • 阻塞及恢复线程:使用 park & unpark来实现线程的暂停和恢复 
  • 维护队列:使用CLH队列,是一个 FIFO的双向链表列。 

4 方法

整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

 需要全套面试笔记的【点击此处即可】即可免费获取

AQS的子类,应当重写以下方法。

方法描述
protected boolean tryAcquire(int arg)独占式获取同步状态
protected boolean tryRelease(int arg)独占式释放同步状态
protected int tryAcquireShared(int arg)共享式获取同步状态
protected boolean tryReleaseShared(int arg)共享式释放同步状态
protected boolean isHeldExclusively()同步状态是否被当前线程所独占

AQS提供了这些模板方法,模板方法将调用上面子类重写后的方法。

 

java

代码解读

复制代码

// 独占式获取同步状态 public final void acquire(int arg) // 与acquire(int arg)相同,但该方法响应中断 public final void acquireInterruptibly(int arg) throws InterruptedException // 在acquireInterruptibly(int arg)基础上增加了超时限制 public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException // 共享式获取同步状态,同一时刻可以有多个线程获得同步状态。 public final void acquireShared(int arg) // 与acquireShared(int arg)相同,但该方法响应中断 public final void acquireSharedInterruptibly(int arg) throws InterruptedException // 在acquireSharedInterruptibly(int arg)基础上,增加了超时限制 public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException // 独占式的释放同步状态 public final boolean release(int arg) // 共享式的释放同步状态 public final boolean releaseShared(int arg) // 获取在同步队列中等待的线程集合 public final Collection<Thread> getQueuedThreads()

5 CLH队列

  • CLH队列是一个FIFO的双向链表:由head、tail、node中间节点组成,每个Node节点包含:thread、waitStatus、next、pre属性。 

    image.png

  • 插入链表都是尾部插入并且setTail为当前节点,同时会阻塞当前线程(调用LockSupport.park方法);
  • 当线程释放同步状态后,会唤醒head节点的next节点,next节点会抢占同步资源,抢占失败后重新阻塞;成功后将next节点setHead。

使用CLH队列的目的在于:

  1. 同步器持有队列的head、tail节点,能快速出队、入队,不用遍历链表
  2. 持有同步资源的线程执行完毕,只会唤醒head节点的next节点,队列中其他节点持续阻塞;这样只有next节点和新到来线程抢占资源,避免不必要的自旋竞争; 

通过Unsafe的CAS操作来更新同步器的head、tail引用 

6 线程入队

6.1 acquire方法实现

  1. 当线程tryAcquire(arg)失败时,被包装为一个Node节点(称为newNode)。 
  2. CAS更新tail引用为newNode失败时,enq(node)方法中通过for (;;)死循环重试,直到入队成功。 
  3. 入队成功后,如果newNode的prev节点是head节点,即newNode有资格抢占同步资源,则尝试获取一次;成功时,更新head引用为newNode,退出;
  4. 失败时,等prev节点的waitStatus==SIGNAL时,将newNode park阻塞,直到被其他线程unpark或interrupt时,继续for循环; 

6.2 入队流程图示

如图,AQS刚初始化时,head、tail都是null。 

 当添加nodo1时,首先创建一个哑元,head、tail都指向它。然后将nodo1加到队尾。 

 acquire(int arg)方法调用流程如下图。 

7 线程出队

出队,即唤醒head节点的next节点,next节点争抢同步资源成功时,更新head引用为自己。

7.1 release实现

注意,release(int arg)exclusive mode下释放资源,同步锁只能由一个线程获得,不会被并发调用。因此不需要for循环+CAS。流程如下:

  • 首先,head节点是获取同步状态成功的节点,release(int arg)就是head节点的线程来唯一调用。
  • 其次,更新head节点引用,是由被唤醒线程在acquireQueued()中通过for (;;)加CAS来实现。
  • 如果head的next节点被cancel了,则从tail向前遍历,找到一个入队最早且状态满足的node,来唤醒它。  unparkSuccessor中没有失败重试 

7.2 releaseShared实现

releaseSharedshared mode下释放资源,存在多线程并发。因此使用了for循环+CAS重试。 

 doReleaseShared()中,可以看到不断重试。 

8 acquire与acquireInterruptibly的区别

两者区别在于:

  • 线程调用acquire()获取同步资源阻塞时,即使被interrupt,也必须等到获取资源成功后,才能调用selfInterrupt();  
  • 线程调用acquireInterruptibly()时,在park状态被interrupt,将抛出InterruptedException而退出for循序;不用等到获取资源成功。  

可见,线程调用acquire()时,如何处理中断(忽略继续或其他)在于自身;调用acquireInterruptibly()时被中断,将得到InterruptedException异常。

9 自定义独占式锁

基于AQS,我们能非常方便地实现一个自定义的独占锁:与ReentrantLock相比,MyExclusiveLock只是不支持锁重入、不支持公平模式,其他并无多少区别。

 

java

代码解读

复制代码

import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; public class MyExclusiveLock implements Lock { // 将对Lock的操作转交给Sync private final Sync sync = new Sync(); // AQS子类,独占模式 private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -4847038481668784725L; @Override protected boolean tryAcquire(int arg) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } @Override protected boolean tryRelease(int arg) { setExclusiveOwnerThread(null); setState(0); return true; } @Override protected boolean isHeldExclusively() { return getState() == 1; } public Condition newCondition() { return new ConditionObject(); } } @Override public void lock() { sync.acquire(1); } @Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } /** * 尝试一次加锁 * * @return 成功否 */ @Override public boolean tryLock() { return sync.tryAcquire(1); } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(time)); } @Override public void unlock() { sync.release(1); } @Override public Condition newCondition() { return sync.newCondition(); } }

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值