查看: 266|回复: 0

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

[复制链接]
发表于 2020-2-3 01:01:10 | 显示全部楼层 |阅读模式
  上一篇简单介绍了AQS,我们大概知道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);
  注意:每一个条件变量内里都维护了一个条件队列
  举个例子,如下所示;
  1. 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();    }}
复制代码


  还可以创建多个条件变量,如下所示,每一个条件变量都维护了一个条件队列:
  1. 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();        // 创建条件变量1        Condition condition1 = lock.newCondition();        //条件变量2        Condition condition2 = lock.newCondition();                // 以下创建两个线程,内里都会获取锁和开释锁        Thread thread1 = new Thread(() -> {            lock.lock();            try {                System.out.println("await begin");[b]//1[/b]                condition1.await();                System.out.println("await end");[b]//5[/b]                                System.out.println("condition2---signal---start");[b]//6[/b]                condition2.signal();                System.out.println("condition2---signal---endend");[b]//7[/b]            } catch (InterruptedException e) {                //            } finally {                lock.unlock();            }        });        Thread thread2 = new Thread(() -> {            lock.lock();            try {                System.out.println("signal begin");[b]//2[/b]                condition1.signal();                System.out.println("signal end");[b]//3[/b]                                System.out.println("condition2---await---start");[b]//4[/b]                condition2.await();                System.out.println("condition2---await---end");[b]//8[/b]            } 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()方法内部看看:
  1. public final void await() throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    //新建一个Node.CONDITION节点放到条件队列末了面    Node node = addConditionWaiter();    //开释当前线程获取的锁    int savedState = fullyRelease(node);    int interruptMode = 0;    //调用park()方法壅闭挂起当前线程    while (!isOnSyncQueue(node)) {        LockSupport.park(this);        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)            break;    }    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)        interruptMode = REINTERRUPT;    if (node.nextWaiter != null) // clean up if cancelled        unlinkCancelledWaiters();    if (interruptMode != 0)        reportInterruptAfterWait(interruptMode);}
复制代码
  1. private Node addConditionWaiter() {    Node t = lastWaiter;    //第一次进来,这个lastWaiter是null,即t = null,不会进入到这个if语句    if (t != null && t.waitStatus != Node.CONDITION) {        unlinkCancelledWaiters();        t = lastWaiter;    }    //创建一个Node.CONDITION范例的节点,然后下面这个if中就是将第一个节点firstWaiter和末了一个节点都指向这个新创建的节点    Node node = new Node(Thread.currentThread(), Node.CONDITION);    if (t == null)        firstWaiter = node;    else        t.nextWaiter = node;    lastWaiter = node;    return node;}
复制代码

  顺便在看看signal方法:
  1. public final void signal() {    if (!isHeldExclusively())        throw new IllegalMonitorStateException();    //条件队列移除第一个节点,然后把这个节点丢到壅闭队列中,然后激活这个线程    Node first = firstWaiter;    if (first != null)        DOSignal(first);}
复制代码


  
  我们想一想在AQS中壅闭队列和条件队列有什么关系啊?
  1.当多个线程调用lock.lock()方法的时候,只有一个线程获取到可锁,其他的线程都会被转为Node节点丢到AQS的壅闭队列中,并做CAS自旋获取锁;
  2.当获取到锁的线程对应的条件变量的await()方法被调用的时候,该线程就会开释锁,并把当前线程转为Node节点放到条件变量对应的条件队列中;
  3.这个时候AQS的壅闭队列中又会有一个节点中的线程能得到锁了,假如这个线程又恰巧调用了对应条件变量的await()方法时,又会重复2的步骤,然后壅闭队列中又会有一个节点中的线程获得锁
  4.然后,又有一个线程调用了条件变量的signal()或者signalAll()方法,就会把条件队列中一个或者所有的节点都移动到AQS壅闭队列中,然后调用unpark方法进行授权,就等着获得锁了;
  一个锁对应一个壅闭队列,但是对应多个条件变量,每一个条件变量对应一个条件队列;其中,这两种队列中存放的都是Node节点,Node节点中封装了线程及其状态,下图所示:

相关技术服务需求,请联系管理员和客服QQ:2753533861或QQ:619920289
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

帖子推荐:
客服咨询

QQ:2753533861

服务时间 9:00-22:00

快速回复 返回顶部 返回列表