浅析AQS (二)--condition的实现

在前一章节中,我们简单分析过aqs中加锁以及阻塞的流程,这一章我们来分析一下condition条件阻塞工具的实现

## 什么是condition

condition是作为条件阻塞器,通过调用await,signal和signalAll方法来阻塞和唤醒线程,可以横向对比的是Object对象的wait,notify以及notifyAll方法,值得注意的是,与Object的wait需要跟synchronized结合使用一样,condition也需要跟锁结合使用,比如ReenTrantLock中的newCondition方法就是创建一个全新的条件阻塞器,而调用await方法也需要通过lock进行加锁才可以正常使用.

## condition.await与Object.wait的区别

* 首先Object相关的阻塞方法都是通过本地方法实现的,而condition的阻塞和唤醒方法都是通过java调用来实现的,其次就是每个Object只能绑定一个阻塞器,即synchronized所绑定的对象,只有通过调用该对象的wait和notify方法才能实现阻塞以及唤醒,并且notify会在调用wait方法的线程中随机挑选一个唤醒

* 而一个lock可以创建多个condition,例如ReentrantLock中的newCondition方法每次调用都会返回一个新的条件阻塞器,这样做的好处是,调用condition方法的signal只会唤醒当前condition调用await方法阻塞的线程,利用这种模式可以实现阻塞队列,如经典的ArrayBlockingQueue就是利用ReenTrantLock创建了两个condition控制队列空时的阻塞以及队列满时的阻塞

## await方法的实现

老规矩先上源码

```java

public final void await() throws InterruptedException {

            //检测线程是否中断

            if (Thread.interrupted())

                throw new InterruptedException();

            //添加一个condition的waiter

            Node node = addConditionWaiter();

            //释放锁

            int savedState = fullyRelease(node);

            //打断模式

            int interruptMode = 0;

            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);

        }

```

await的实现并不难理解,而操作的Node节点与AQS中的node节点是一个对象,通过标记node的waitStatus变量来判断当前node的状态,我们再来看看addConditionWaiter的实现

```java

private Node addConditionWaiter() {

        Node t = lastWaiter;

        //如果尾部的等待node被取消了,则遍历取消所有的被取消的节点

        if (t != null && t.waitStatus != Node.CONDITION) {

            unlinkCancelledWaiters();

            t = lastWaiter;

        }

        //创建一个condition状态的node节点

        Node node = new Node(Thread.currentThread(), Node.CONDITION);

        //如果尾结点是空证明是一个空队列,将头结点设置为当前节点,否则将当前节点插入当前尾节点的后面

        if (t == null)

            firstWaiter = node;

        else

            t.nextWaiter = node;

        lastWaiter = node;

        return node;

}

private void unlinkCancelledWaiters() {

            Node t = firstWaiter;

            Node trail = null;

            //遍历取消所有节点状态不是condition的节点

            while (t != null) {

                Node next = t.nextWaiter;

                if (t.waitStatus != Node.CONDITION) {

                    t.nextWaiter = null;

                    if (trail == null)

                        firstWaiter = next;

                    else

                        trail.nextWaiter = next;

                    if (next == null)

                        lastWaiter = trail;

                }

                else

                    trail = t;

                t = next;

            }

        }

```

在这个方法中,值得注意的是通过condition维护的队列,与aqs中排队的队列是两个完全不同的队列,condition的队列维护在condition对象中,通过firstWaiter和lastWaiter变量来维护队列的头与尾,我们继续往下看fullyRelease方法

```java

    final int fullyRelease(Node node) {

        boolean failed = true;

        try {

            int savedState = getState();

            if (release(savedState)) {

                failed = false;

                return savedState;

            } else {

                throw new IllegalMonitorStateException();

            }

        } finally {

            if (failed)

                node.waitStatus = Node.CANCELLED;

        }

    }

```

fullyRelease方法可见是直接释放当前独占锁,在java中目前只有ReenTrantLock以及ReentrantReadWriteLock,实现了newCondition方法,所以共享锁是不允许condition阻塞的,

继续向下看isOnSyncQueue方法,顾名思义该方法是判断当前node是否在同步队列总

```java

final boolean isOnSyncQueue(Node node) {

        if (node.waitStatus == Node.CONDITION || node.prev == null)

            return false;

        if (node.next != null) // If has successor, it must be on queue

            return true;

        return findNodeFromTail(node);

    }

```

首先是校验当前节点的状态,如果节点状态还是condition那么一定没有插入队列中,而同样node.prev前面节点如果为空自然是也没有插入队列的,后续判断node.next同样是判断后续有没有等待节点,这里值得注意的是,node.next是同步队列节点的下一个节点,而condition阻塞队列的节点为nextWaiter不要弄混了,

如果这两步判断没有成功的话,说明当前节点的prev节点不为空,而next节点为空,而node.prev节点不为空,,但是还没有在队列上,因为有可能cas失败,所以要从尾部遍历一遍确定在没在节点中.

如果在同步队列中则调用acquireQueued尝试获取锁或者排队,接下来就是判断是否打断等流程,后续不在赘述,接下来我们康康signal方法

```java

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;

                //将当前节点插入阻塞队列中

            } while (!transferForSignal(first) &&

                    (first = firstWaiter) != null);

        }

final boolean transferForSignal(Node node) {

        //如果换失败,则只有可能是被取消了

        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))

            return false;

        //将当前线程插入队列,并返回node节点前面的节点,

        Node p = enq(node);

        int ws = p.waitStatus;

        //修改前一个节点的状态为signle以便被唤醒

        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))

            //如果线程被取消了,或者将waitstatus修改失败的话,说明当前线程已经被取消了

            LockSupport.unpark(node.thread);

        return true;

    }

```

首先调用signal时一定是要以独占锁的模式调用,否则会抛出异常,然后将当前等待节点后移,并且将当前的节点插入阻塞队列中,不需要唤醒线程因为调用signal时一定是已经被某一线程获取了锁,而当调用release时会释放锁并且自动调用后续的锁

那么这里有一个问题就是为什么会在这里调用一次unpark,就算不调用,等到下次唤醒的时候,也会清除掉被取消的节点,这里我查阅资料发现,这次唤醒主要是提升性能,在这里唤醒一次,将前面取消的节点都删除,以便下次唤醒不需要在删除节点.这里加不加这个唤醒逻辑上是一样的

我们再来看看signalAll方法

```java

      public final void signalAll() {

            if (!isHeldExclusively())

                throw new IllegalMonitorStateException();

            Node first = firstWaiter;

            if (first != null)

                doSignalAll(first);

        }

      private void doSignalAll(Node first) {

            lastWaiter = firstWaiter = null;

            do {

                Node next = first.nextWaiter;

                first.nextWaiter = null;

                transferForSignal(first);

                first = next;

            } while (first != null);

        }

```

这里实现与signal几乎相同,只不过一个是将first节点插入队列,而signalAll方法则是将后续队列全部插入同步队列中

到这里我们就已经将condition的实现完全理清了,后续我们也会再分析利用condition来实现的同步阻塞队列ArrayBlockingQueue

你可能感兴趣的:(浅析AQS (二)--condition的实现)