乐趣区

关于java:JAVA并发编程AbstractQueuedSynchronizerAQS的实现原理

一、AQS 根本介绍

同步器 AbstractQueuedSynchronizer(简称 AQS)是用来 构建其余同步组件的根底 ,它应用了一个 int 变量来示意 同步状态 state,通过 内置的 FIFO 队列 来实现 线程的排队工作

二、如何应用 AQS 来构建同步组件?

同步器的设计是基于 模板模式 的,使用者继承同步器并重写指定的办法。同步器可重写的办法如下:

办法名称 形容
boolean tryAcquire(int arg) 尝试独占式获取同步状态,实现该办法须要查问以后状态并判断同步状态是否合乎预期,而后再进行 CAS 设置同步状态
boolean tryRelease(int arg) 尝试独占式开释同步状态,期待获取同步状态的线程将有机会获取同步状态
int tryAcquireShared(int arg) 尝试共享式获取同步状态,返回大于等于 0 的值,示意获取胜利,反之获取失败。
boolean tryReleaseShared(int arg) 尝试共享式开释同步状态
boolean isHeldExclusively() 以后同步器是否在独占模式下被线程占用,个别该办法示意是否被以后线程独占

实现自定义同步组件时,将会调用同步器提供的模板办法,如下:

办法名称 形容
void acquire(int arg) 独占式获取同步状态 ,如果以后线程获取同步状态胜利,则由该办法返回,否则将会 进入同步队列期待,该办法会调用重写的 tryAcquire 办法
void acquireInterruptibly(int arg) 与 acquire 雷同,然而该办法 响应中断。如果以后线程未获取到同步状态而进入同步队列,如果以后线程被中断,则该办法会抛出 InterruptedException 异样
boolean tryAcquireNanos(int arg, long nanos) 在 acquireInterruptibly 根底上减少了 超时限度,如果以后线程在超时工夫内没有获取到同步状态,则返回 false,反之返回 true
void acquireShared(int arg) 共享式地获取同步状态,如果以后线程未获取到同步状态,将会进入同步队列期待,与独占式次要区别在于同一时刻能够有多个线程获取到同步状态
void acquireSharedInterruptibly(int arg) 与 acquireShared 雷同,该办法响应中断
boolean tryAcquiredSharedNanos(int arg, long nanos) 在 acquireSharedInterruptibly 根底上减少了超时限度
boolean release(int arg) 独占式的开释同步状态,该办法在开释同步状态之后,将同步队列的第一个节点的线程唤醒
boolean releaseShared(int arg) 共享式的开释同步状态
Collection getQueuedThreads() 获取期待在同步队列上的线程汇合

三、AQS 的实现剖析

从实现角度来剖析同步器是如何实现线程同步?

  1. 同步队列
  2. 独占式同步状态获取与开释
  3. 共享式同步状态获取与开释
  4. 超时获取同步状态

3.1 同步队列

AQS 外部基于同步队列(一个双向队列)来实现同步状态的治理。以后线程获取同步状态失败时,会将以后线程以及期待状态等信息结构成为一个 Node 节点并将其退出同步队列,同时阻塞以后线程,当同步状态开释,会把首节点中的线程唤醒,使其再次获取同步状态。

Node 类构造如下:

属性类型与名称 形容
int waitStatus 期待状态。1、CANCELLED,值为 1,因为在同步队列中期待的线程期待超时或者被中断,须要从同步队列中勾销期待,节点进入该状态将不会变动。2、SIGNAL,值为 -1,后继节点的线程处于期待状态,而以后节点的线程如果开释了同步状态或被勾销,将会告诉后继节点,使后继节点的线程得以运行。3、CONDITION,值为 -2,节点在期待队列中,节点线程期待在 Condition 上,当其余线程对 Condition 调用了 signal()办法后,该节点将会从期待队列中转移到同步队列中,退出到对同步状态的获取中。4、PROPAGATE,值为 -3,示意下一次共享式同步状态获取将会无条件的被流传上来。5、INITIAL,值为 0,初始状态
Node prev 前驱节点,当节点退出同步队列时被设置
Node next 后继节点
Node nextWaiter 期待队列(后续章节会介绍)中的后继节点。如果以后节点是共享的,那么这个字段是 SHARED 常量。也就是说节点类型(独占或共享)和期待队列中的后继节点共用同一个字段
Thread thread 对应的线程

Node 节点是形成同步队列的根底,没有胜利获取同步状态的线程将会成为节点退出该队列的尾部,头节点是领有同步状态的节点。同步队列的根本构造如下:

3.2 独占式获取、开释同步状态

获取同步状态:通过调用同步器的 acquire(int arg)办法获取同步状态,该办法无奈响应中断。下述代码实现了同步状态获取、节点结构、退出同步队列等相干工作。

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

流程如下:

  1. 调用自定义同步器的 tryAcquire(int arg)办法,该办法保障线程平安的获取同步状态,如果获取胜利,则设置独占线程为以后线程,如果获取失败,结构节点并通过 addWaiter(Node node)办法将该节点退出到同步队列的尾部
  2. 接着调用 acquireQueued(Node node, int arg)办法,使得节点再次尝试获取同步状态
  3. 如果获取不到会调用 LockSupport.park()办法阻塞以后线程,而 被阻塞线程的唤醒 次要依附 前驱节点的出队或阻塞线程被中断 来实现。

开释同步状态:以后线程获取同步状态并执行了相应逻辑后,就须要开释同步状态,使得后续节点可能持续获取同步状态。通过调用同步器的 release(int arg) 办法就能够开释同步状态,该办法在开释同步状态后,会唤醒其后继节点

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

总结:

  1. 获取同步状态时,同步器保护一个同步队列,获取状态失败的线程会被退出到队列尾部。这里会先判断一下前驱节点是否是头节点,如果是,则尝试获取同步状态,如果获取胜利,间接将该节点设置为头节点,如果获取失败,将以后线程挂起。
  2. 开释同步状态时,同步器调用 release(int arg)办法开释同步状态,唤醒头节点的后继节点

3.3 共享式获取、开释同步状态

通过调用同步器的 acquireShared(int arg) 办法能够共享式地获取同步状态。

public final void acquireShared(int arg) {if(tryAcquireShared(arg) < 0) 
        doAcquireShared(arg);
}

private void doAcquireShared(int arg) {final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for(;;) {final Node p = node.predecessor();
            if(p == head) {int r = tryAcquireShared(arg);
                if(r >= 0) {setHeadAndPropagate(node, r);
                    p.next = null;
                    if(interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if(shouldParkAfterFailedAcquire(p, node) 
                && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {if(failed) {cancelAcquire(node);
        }
    }
}

在 acquireShared(int arg)办法中,同步器调用 tryAcquireShared(int arg)办法尝试获取同步状态,办法返回值为 int 值,如果大于等于 0,示意可能获取到同步状态,反之无奈获取同步状态 。在 doAcquireShared(int arg) 办法的自旋过程中,如果以后节点的前驱为头节点,尝试获取同步状态,如果返回值大于等于 0,示意该次获取同步状态胜利并从自旋过程中退出。

与独占式一样,共享式也须要开释同步状态,通过调用 releaseShared(int arg)办法能够开释同步状态。它与独占式地次要区别在于 tryReealseShared(int arg)办法必须确保同步状态平安开释,个别是通过 循环 +CAS来保障的。

public final boolean releaseShared(int arg) {if(tryReleaseShared(arg)) {doReleaseShared();
        return true;
    }
    return false;
}

3.4 超时获取同步状态

通过调用同步器的 doAcquireNanos(int arg, long nanosTimeout) 办法能够超时获取同步状态,在指定的时间段内获取同步状态,如果获取到则返回 true,否则返回 false。

超时获取同步状态过程 响应中断获取同步状态过程 的升级版,doAcquireNanos 办法在 反对响应中断的根底上,减少了超时获取的个性,该办法提供了synchronized 关键字不具备的个性

独占式超时获取同步状态和独占式获取同步状态在流程上十分相似,次要区别在于未获取到同步状态时的解决逻辑。acquire(int arg)在未获取到同步状态时,将会使以后线程始终处于期待状态,而 doAcquireNanos()办法会使以后线程期待 nanosTimeout 纳秒,如果以后线程在 nanosTimeout 纳秒内没有获取到同步状态,将会间接返回 false。

四、总结

自定义同步组件可通过继承 AQS 并实现它的形象办法来治理 同步状态 。同步器既能够反对 独占式 地获取同步状态,也能够反对 共享式 地获取同步状态,来实现不同类型的同步组件(如 ReentrantLock 可重入锁、ReentrantReadWriteLock 读写锁和 CountDownLatch 锁存器)

退出移动版