AQS基础入门

什么是AQS?

AQS是AbstractQueuedSynchronizer的简称。直译就是“抽象队列同步器”。它定义了一套多线程访问共享资源的同步框架,需要同步类实现都依赖于它,如ReentrantLock、ReentrantReadWriteLock、StampedLock、CountDownLath、CyclicBarrier等
在这里插入图片描述
它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。state的访问方式有三种:

  1. getState():获取state的值。
  2. setState():方法通常用于当前正持有锁的线程对state变量进行修改,不存在竞争,是线程安全的,所以此处没必要用CAS保证原子性,修改的性能更重要。
  3. compareAndSetState():通常用于在获取到锁之前,尝试加锁时,对state进行修改,这种场景下,由于当前线程不是锁持有者,所以对state的修改是线程不安全的,也就是说可能存在多个线程都尝试修改state,所以需要保证对state修改的原子性操作,即使用了unsafe类的本地CAS方法:

基本流程

1.多线程请求资源过程中(acquire)

  1. 在多线程环境当中,如果一个线程抢夺到了共享资源,那么讲当前的请求资源的线程设为有效的工作线程。并将把共享资源状态改为锁定状态。
  2. 共享资源被锁定,其他没有抢夺到线程,将被包装成一个node节点,然后自旋直到自旋成功放入同步队列的尾部

2.对于获取锁的线程,移出等待队列(release)

  1. 获取锁的线程释放共享资源,修改state的状态
  2. 如果新的状态允许某个被阻塞的线程获取成功则解除队列中一个或多个线程的阻塞状态

AQS的主要数据结构

组件数据结构
同步状态volatile int state
阻塞LockSupport类
同步队列Node节点
条件队列ConditionObject

LockSupport

介绍

LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。

主要api接口
public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复当前线程
public static Object getBlocker(Thread t);
与wait/notify对比
  1. wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象,但是park不需要获取某个对象的锁就可以锁住线程。
  2. notify只能随机选择一个线程唤醒,无法唤醒指定的线程,unpark却可以唤醒一个指定的线程。
  3. 因为中断的时候park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,然后做额外的处理
  4. unpark函数 可以先于 park 调用。比如线程B 调用 unpark函数,给线程A 发了一个“许可”,那么当线程A 调用 park 时,它发现已经有“许可”了,那么它会马上再继续运行。举例:比如线程B 连续调用了三次 unpark函数,当线程A 调用 park函数就使用掉这个“许可”,如果线程A 再次调用 park,则进入等待状态。

同步队列

Node节点的数据结构

在这里插入图片描述
这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个 Node 其实是由线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点线程 。

入队

默认的AQS的head和tail都指向一个空节点:
在这里插入图片描述
当有新的等待节点入队时,由于CLH是FIFO队列,故在当前队列的尾节点插入。在加入队列这个操作中,同时可能有多个node需要加入,所以一定要保证线程安全,因此同步器提供了一个CAS方法,它需要传递当前线程期待的尾节点和当前节点,只有符合要求才会设置成功
在这里插入图片描述

出队

同因为是FIFO,获取到AQS同步状态的必然是首节点,当首节点的线程释放同步状态时,会唤醒后续节点,而后续节点会在获取AQS同步状态成功的时候将自己设置为首节点。设置首节点的动作是由获取到同步状态成功的线程来完成。
这里好像没有CAS操作?确实没有,因为每次只有一个线程能够成功获取到同步状态,所以不需要像入队那样做CAS操作
在这里插入图片描述

条件队列

AQS除了同步队列,还包含一个条件队列。AQS只有一个同步队列,但可以有多个条件队列。条件队列指的是ConditionObject类,主要是给维护独占同步的类以及实现Lock接口的类使用

条件队列移动到同步队列(signal)

在这里插入图片描述
如图所示,条件队列有自己单独的队列进行管理。当调用signal操作时,将条件队列中的节点从条件队列转移到同步队列中

同步队列移动到条件队列(await)

await操作就是当前线程节点同同步队列进入条件队列进行等待,大致示意图如下:
在这里插入图片描述
在同步队列中释放节点,让后将节点添加到条件队列的队尾中。

https://blog.youkuaiyun.com/qq_32828253/article/details/111565450?spm=1001.2014.3001.5502

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值