Java多线程(五)AQS-抽象队列同步器解析

2019-10-13  本文已影响0人  GIT提交不上

一、同步器设计

  AQS,全称为AbstractQueuedSynchronizer(抽象队列同步器),是Java多线程显式锁(Lock)的底层实现。同步器的设计属于独占模式:资源是独占的,一次只能一个线程获取。同步器的具体设计方案如下:

  1. 定义一个变量int state=0,变量表示为被获取的资源数量。
  2. 线程在获取资源前先检查state的状态,如果为0,则修改为1,表示获取资源成功,否则表示资源已经被其他线程占用,此时线程要堵塞以等待其他线程释放资源
  3. 为了能使得资源释放后找到那些为了等待资源而堵塞的线程,我们把这些线程保存在FIFO队列中。
  4. 当占有资源的线程释放掉资源后,可以从队列中唤醒一个堵塞的线程,由于此时资源已经释放,因此这个被唤醒的线程可以获取资源并且执行。

参考链接:JUC解析-AQS(1)

  AQS模型如图1-1所示:

图1-1 AQS模型.png

二、AQS成员变量

  AQS底层实现是双向链表的数据结构,通过查看源码,其主要包含的成员变量包括:

//状态变量state
private volatile int state;
//双向链表表头
private transient volatile Node head;
//双向链表表尾
private transient volatile Node tail;

  Node类源码如下所示:

static final class Node {
    //标记一个结点(对应的线程)在共享模式下等待
    static final Node SHARED = new Node();
   // 标记一个结点(对应的线程)在独占模式下等待
    static final Node EXCLUSIVE = null;

    //waitStatus的值,表示该结点(对应的线程)已被取消
    static final int CANCELLED =  1;
    //waitStatus的值,表示后继结点(对应的线程)需要被唤醒
    static final int SIGNAL    = -1;
    //waitStatus的值,表示该结点(对应的线程)在等待某一条件
    static final int CONDITION = -2;
    //waitStatus的值,表示有资源可用,新head结点需要继续唤醒后继结点
    static final int PROPAGATE = -3;

    volatile int waitStatus;  //等待状态
    volatile Node prev;  //前驱节点
    volatile Node next;  //后继节点
    volatile Thread thread;  //节点对应的线程
    
    Node nextWaiter;  //等待队列下一个等待的节点
    //其余省略
    ... ... ... ...
}

三、acquire & release资源获取模式

   acquire是一种以独占方式获取资源,如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。acquire方法如下所示:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

1.tryAcquire()尝试直接去获取资源,如果成功则直接返回;
2.addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
3.acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
4.如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

  release方法会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。release方法源码如下所示:

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

  如果资源释放成功,调用unparkSuccessor(Node)方法唤醒等待队列中下一个线程。这里要注意的是,下一个线程并不一定是当前节点的next节点,而是下一个可以用来唤醒的线程,如果这个节点存在,调用unpark()方法唤醒。unparkSuccessor方法源码如下所示:

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

参考链接:Java技术之AQS详解
未完待续

上一篇下一篇

猜你喜欢

热点阅读