写点什么

看看 AQS 阻塞队列和条件队列

  • 2021 年 11 月 12 日
  • 本文字数:2150 字

    阅读完需:约 7 分钟


我们大概知道 AQS 就是一个框架,把很多功能都给实现了(比如入队规则,唤醒节点中的线程等),我们如果要使用的话只需要实现其中的一些方法(比如 tryAcquire 等)就行了!这次主要说说 AQS 中阻塞队列的的入队规则还有条件变量;


一.AQS 入队规则


我们仔细分析一下 AQS 是如何维护阻塞队列的,在独占方式获取资源的时候,是怎么将竞争锁失败的线程丢到阻塞队列中的呢?


我们看看 acquire 方法,这里首先会调用子类实现的 tryAcquire 方法尝试修改 state,修改失败的话,说明线程竞争锁失败,于是会走到后面的这个条件;


这个 addWaiter 方法就是将当前线程封装成一个 Node.EXCLUSIVE 类型的节点,然后丢到阻塞队列中;



第一次还没有阻塞队列的时候,会到 enq 方法里面,我们仔细看看 enq 方法



enq()方法中,我们在第一次进入这个方法的时候,下面图一所示,tail 和 head 都指向 null;


第一次循环,到首先会到图二,然后判断 t 所指向的节点是不是 null,如果是的话,就用 CAS 更新节点,这个 CAS 我们可以看作:头节点 head 为 null,我们把 head 节点更新为一个哨兵节点(哨兵节点就是 new?Node()),再将 tail 也指向 head,就是图三了



第二次 for 循环:走到上面的 else 语句,将新节点的前一个节点设置为哨兵节点;



然后就是 CAS 更新节点,这里 CAS 的意思:如果最后的节点 tail 指向的和 t 是一样的,那么就将 tail 指向 node 节点



最后再将 t 的下一个节点设置为 node,下图所示,就 ok 了



二.AQS 条件变量的使用


什么是条件变量呢?我们在开始介绍 AQS 的时候,还有一个内部类没有说,就是 ConditionObject,还记得前面说过的 Unsafe 中的 park 和 unpark 方法吗?而这个 ConditionObject 就对这两个方法进行了一次封装,await()和 signal()方法,但是更灵活,可以创建多个条件变量,每个条件变量维护一个条件队列(就是一个单向链表,可以看到 Node 这个内部类中个属性是 nextWaiter);


注意:每一个条件变量里面都维护了一个条件队列


举个例子,如下所示;


package com.example.demo.study;


import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;


public class Study0201 {


public static void main(String[] args) throws InterruptedException {// 创建锁对象 ReentrantLock lock = new ReentrantLock();// 创建条件变量 Condition condition = lock.newCondition();// 以下创建两个线程,里面都会获取锁和释放锁 Thread thread1 = new Thread(() -> {lock.lock();try {System.out.println("await begin");// 注意,这里调用条件变量的 await 方法,当前线程就会丢到 condition 条件变量中的条件队列中阻塞 condition.await();System.out.println("await end");} catch (InterruptedException e) {//} finally {lock.unlock();}


});


Thread thread2 = new Thread(() -> {lock.lock();try {System.out.println("signal begin");// 唤醒被 condition 变量内部队列中的某个线程 condition.signal();System.out.println("signal end");} finally {lock.unlock();}});thread1.start();Thread.sleep(500);thread2.start();}}



还可以创建多个条件变量,如下所示,每一个条件变量都维护了一个条件队列:


package com.example.demo.study;


import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;


public class Study020


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


1 {


public static void main(String[] args) throws InterruptedException {// 创建锁对象 ReentrantLock lock = new ReentrantLock();// 创建条件变量 1Condition condition1 = lock.newCondition();//条件变量 2Condition condition2 = lock.newCondition();


// 以下创建两个线程,里面都会获取锁和释放锁 Thread thread1 = new Thread(() -> {lock.lock();try {System.out.println("await begin");//1condition1.await();System.out.println("await end");//5


System.out.println("condition2---signal---start");//6condition2.signal();System.out.println("condition2---signal---endend");//7} catch (InterruptedException e) {//} finally {lock.unlock();}


});


Thread thread2 = new Thread(() -> {


lock.lock();try {System.out.println("signal begin");//2condition1.signal();System.out.println("signal end");//3


System.out.println("condition2---await---start");//4condition2.await();System.out.println("condition2---await---end");//8} catch (InterruptedException e) {//} finally {lock.unlock();}


});


thread1.start();Thread.sleep(500);thread2.start();


}


}



三.走进条件变量


我们看看上面的获取条件变量的方式 Condition condition1 = lock.newCondition(),我们打开 newCondition 方法,最后就是创建一个 ConditionObject 实例;这个类是 AQS 的内部类,通过这个类可以访问 AQS 内部的属性和方法;


注意:在调用 await 方法和 signal 方法之前,必须要先获取锁




然后我们再看看条件变量的 await 方法,下图所示,我们可以进入到 addConditionWaiter()方法内部看看:


public final void await() throws InterruptedException {if (Thread.interrupted())

评论

发布
暂无评论
看看AQS阻塞队列和条件队列