关于AQS介绍

关于AQS介绍

AbstractQueuedSynchronizer内部数据结构

在AbstractQueuedSynchronizer内部,有一个队列,我们把它叫做同步等待队列。它的作用是保存等待在这个锁上的线程(由于lock()操作引起的等待)。

此外,为了维护等待在条件变量上的等待线程,AbstractQueuedSynchronizer又需要再维护一个条件变量等待队列,也就是那些由Condition.await()引起阻塞的线程。由于一个重入锁可以生成多个条件变量对象,因此,一个重入锁就可能有多个条件变量等待队列。实际上,每个条件变量对象内部都维护了一个等待列表。

关于AQS介绍_第1张图片

下面的类图展示了代码层面的具体实现:

关于AQS介绍_第2张图片

无论是同步等待队列,还是条件变量等待队列,都使用同一个Node类作为链表的节点。

Node节点另外一个重要的成员是waitStatus,它表示节点等待在队列中的状态:

  • CANCELLED:表示线程取消了等待。如果取得锁的过程中发生了一些异常,则可能出现取消的情况,比如等待过程中出现了中断异常或者出现了timeout CANCELLED=1
  • SIGNAL:表示后续节点需要被唤醒。SIGNAL=-1
  • CONDITION:线程等待在条件变量队列中。CONDITION=-2
  • 0: 初始状态

在具体的实现中,就可以简单的通过waitStatus是否小于等于0,来判断是否是CANCELLED状态。

源码解析

acquire

获得请求许可的代码:

public final void acquire(int arg) {
    //尝试获得许可, arg为许可的个数。对于重入锁来说,每次请求1个。
    if (!tryAcquire(arg) &&
    // 如果tryAcquire 失败,则先使用addWaiter()将当前线程加入同步等待队列
    // 然后继续尝试获得锁
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
}
// 进入一步看一下tryAcquire()函数。
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
// 该函数的作用是尝试获得一个许可。对于AbstractQueuedSynchronizer来说,这是一个未实现的抽象函数。
// 具体实现在子类中。在重入锁,读写锁,信号量等实现中,都有各自的实现。

如果tryAcquire()成功,则acquire()直接返回成功。如果失败,就用addWaiter()将当前线程加入同步等待队列。

addWaiter

// 接下来进入 addWaiter 逻辑,构造 Node 队列
private Node addWaiter(Node mode) {
    // Node维护当前线程对象
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    // 将节点加入到队列尾端,这是一个快速方法compareAndSetTail,可能会失败
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果快速失败,就使用enq()函数,将node加入队列的尾部
    enq(node);
    return node;
}

接着, 对已经在队列中的线程请求锁,使用acquireQueued()函数,从函数名字上可以看到,其参数node,必须是一个已经在队列中等待的节点。它的功能就是为已经在队列中的Node请求一个许可。无论是普通的lock()方法,还是条件变量的await()都会使用这个方法.

acquireQueued

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取当前节点的前驱结点,在下图所示中,其中第一个 Node 称为 Dummy(哑元)或哨兵,
            // 用来占位,并不关联线程
            // 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 请求失败,需要阻塞当前线程,由shouldParkAfterFailedAcquire()进行判断  
			// 简单的理解,对于前序节点是SIGNAL的,都需要park。对于已经CANCEL的节点,进行跳过删除。
            // 对于初始节点和PROPAGATE节点,则设置为SIGNAL
            if (shouldParkAfterFailedAcquire(p, node) &&
                // park(),并且判断是不是等待过程中发生中断
                parkAndCheckInterrupt())
                // 如果发生中断,将中断信息保存
                interrupted = true;
        }
    } finally {
        if (failed)
            // 如果意外失败了,比如异常之类的,那么久取消这个请求
            cancelAcquire(node);
    }
}

关于AQS介绍_第3张图片

关于AQS介绍_第4张图片

signal

条件变量等待

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

// 设置Condition.await()
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 将节点加入到条件变量等待队列
    Node node = addConditionWaiter();
    // 进入等待队列之前,要释放自己所持有的的许可证,也可以叫做释放同步器上的锁(在lock里面)
    // 同时唤醒同步等待队列第二个节点
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 判断当前节点是否在同步队列中
    while (!isOnSyncQueue(node)) {
       // 当前节点不再同步队列中,直接park挂起
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 到这里,表示我已经从等待中唤醒(有人signal我了),并且已经被放到同步等待队列中了
    // 所以既然已经在同步队列中,就可以直接用acquireQueued()再次请求许可了
	// 从await()中唤醒的线程,必须,一定要再次获得许可才行。
    // 之前释放几个现在就要拿回来几个,不然lock()和unlock()数量就对不上号了。
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程 创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部

关于AQS介绍_第5张图片

signal

Condition对象的signal()通知

signal()通知的时候,是在条件等待队列中,按照FIFO进行,首先从第一个节点下手

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 从第一个节点开始
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
        // transferForSignal()把条件等待队列中的元素,移动到同步等待队列尾端
		// 这样,当前面有许可可以使用时,它就可以自动被唤醒了,对于移动过程中,如果是一个已经CANCEL的节点,那么也会直接被唤醒
        // transferForSignal()可能失败原因 节点被打断或者超时,放弃对许可证的获取
    } while (!transferForSignal(first) &&(first = firstWaiter) != null);
}

release

release()释放锁

public final boolean release(int arg) {
    //tryRelease()是一个抽象方法,在子类中有具体实现和tryAcquire()一样
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 从队列中唤醒一个等待中的线程(遇到CANCEL的直接跳过)
            unparkSuccessor(h);
            return true;
    }
    return false;
}

AbstractQueuedSynchronizer 是一个比较复杂的实现,里面每一个方法和实现都值得慢慢推敲,要完全理解其中的细节还需要慢慢琢磨。

你可能感兴趣的:(java,开发语言,后端)