关于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锁存器)

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理