AQS源码学习记录: 二、ConditionObject源码分析

ConditionObject

ConditionObject是AQS中的内部类,用于线程间通信,可以精确地挂起或者唤醒某个线程。

ConditionObject中的成员变量是两个Node,分别构成FIFO队列的头节点和尾节点,condition队列中Node出队/入队,由每个节点Node中的等待状态( static final int CONDITION = -2)进行控制。
 // condition queue的头节点
 private transient Node firstWaiter;
 // condition queue的尾节点
 private transient Node lastWaiter;

一、awaite()方法

先看下ConditionObjectawaite()方法,它和Objectwaite()方法一样,都是让线程等待,并且会释放锁。

当前线程在调用 await()方法后,会响应中断(抛中断异常),加入到条件队列,此时需要释放锁(能调用await方法的线程,必定持有了独占锁),且记录释放前锁状态,因为当线程重新被唤起时,需要通过 acquireQueued()方法申请锁,成功则,重新恢复锁状态。

该方法中,会判断当前线程是否在同步队列中。一个线程在同步队列中有两种可能:
1、当前Thead在条件队列中被中断,checkInterruptWhileWaiting()方法会将其加入同步队列。
2、其它线程调用signal(),会转移到同步队列中

public final void await() throws InterruptedException {
            //该方法会响应中断
            if (Thread.interrupted())
                throw new InterruptedException();
            //将当前线程添加到条件队列中
            Node node = addConditionWaiter();
            //线程调用await()方法时,是持有锁的,这里需要将其释放,并记录线程释放前锁状态
            //释放锁,当后继未取消节点获取锁后,当前线程Node会移出同步队列
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //判断当前线程是否在同步队列
            while (!isOnSyncQueue(node)) {
                //在此挂起当前线程
                LockSupport.park(this);
                // 如果当前线程被中断,则将Node加入同步队列 
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //走到这里说明当前线程在同步队列中,获取锁后,恢复锁释放前状态,返回线程中断状态。
            //interruptMode != THROW_IE,表明不是在条件队列的等待过程中被中断
            //有可能是被signal()唤醒,此时interruptMode==REINTERRUPT
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
           //通过 "node.nextWaiter != null" 表明,当前Node 在 Condition Queue 与 Sync Queue 里面都会存在,
           //说明当前线程被唤醒方式是中断,而不是通过signal()
            if (node.nextWaiter != null) // 清除cancel节点(非CONDITION状态节点)
                unlinkCancelledWaiters();
           //如果当前线程被中断,则根据线程的中断模式来作对应处理
           //在条件队列中等待被中断:THROW_IE,则抛出异常
           //被signal()唤醒,REINTERRUPT则重新设回中断状态
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

接下来挨个分析await()方法中调用的各种方法:

1.addConditionWaiter()

该方法负责将新的node插入条件队列未被取消的尾部节点后面,作为新的lastWaiter。
private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter是取消状态,则直接踢出队列
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters(); //从头节点开始依次找寻被取消的节点,踢出条件队列
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            //lastWaiter==null,表明条件队列不存在,需要初始化,当前节点指向firstWaiter和lastWaiter
            //否则,直接插入条件队列尾部,作为新的lastWaiter
            if (t == null)
                firstWaiter = node;
            else 
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

2.fullyRelease()

该方法用释放当前线程持有的锁,并返回释放锁前的锁状态,释放锁失败,则设置当前Node为取消状态
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState(); // 获取锁状态
            if (release(savedState)) {//释放锁
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            //当重入锁,重入后,锁状态sate大于1,此时release(savedState)返回false,进入else,抛异常,failed=true
            //释放锁失败,当前Node的等待状态设置为取消 
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

3.checkInterruptWhileWaiting(Node node)

该方法用于检测当前线程中断,如果signalled之前被中断,则返回THROW_IE,signalled之后被中断,返回REINTERRUPT,未中断返回0
private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

4.transferAfterCancelledWait(Node node)

该方法用于取消线程在条件队列中等待,且确保线程能进入同步队列。
返回值true:表示当前线程是在被signaled前发生了中断;false:表示当前线程是在signaled后发生了中断
final boolean transferAfterCancelledWait(Node node) {
        //signal之前被中断,取消CONDITION状态
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node); //将当前线程Node加入到同步队列中
            return true;
        }
        /*
         * 上面if如果不满足,很可能是另一个线程执行了signal(),导致node状态已经被改为0了,
         * 这个时候,node正在入同步队列的过程中
         * 此时当前线程让出资源,不需要处理,等待signal()执行完成,
         * 这边检测到线程加入到同步队列中后,就返回false
         */
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

5.isOnSyncQueue(Node node)

该方法用于检测条件队列中的Node是否也存在与CLH同步队列中。
当在条件队列中等待的线程,被中断,或者被其它线程signal时,均会加入同步队列
final boolean isOnSyncQueue(Node node) {
        //waitStatus为CONDITION,肯定不在同步队列,因为进入同步队列,ws改为0
        // node.prev为null,也不可能在同步队列,因为同步队列中至少有头节点
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
         // 有后继节点,肯定在同步队列中,因为仅在同步队列中,才会给node.next设值
        if (node.next != null)
            return true;
        /*
        * 如果以上条件都不满足,那么这个node可能正在通过CAS设置为同步队列tail节点
        * 但是还没来得及设置next值,也有可能是CAS入队压根没成功,这个时候就需要从
        * 同步队列尾部开始往前遍历,查找该node节点
        */
        return findNodeFromTail(node);
    }

二、signal()方法

调用该方法的线程可以去唤醒当前condition队列中第一个等待的线程
        public final void signal() {
            //当前线程要唤醒另一个线程,那么当前线程必须持有锁
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
doSignal(Node first)方法用于在condition队列中,从队头开始寻找一个未被取消的node,进行唤醒。
        private void doSignal(Node first) {
            do {
                //如果是 first.nextWaiter==null,说明队列是空的,那么直接将lastWaiter置空
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                //准备被唤醒的node先出队
                first.nextWaiter = null;
                //如果当前节点被取消,且后继节点仍存在,则一值循环
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
transferForSignal(Node node)将node节点从condition队列移动到AQS阻塞队列,成功返回true。
final boolean transferForSignal(Node node) {
        // 如果当前线程被取消,那么设置状态失败,返回false
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //将当前node加入到同步队列尾部,并返回前驱节点
        Node p = enq(node);
        int ws = p.waitStatus;
        //如果前驱节点被取消(ws > 0)或前驱节点的等待状态未成功设未signal,那么立即唤醒当前线程
        // 如果不唤醒,那么当前线程将无人去唤醒它去竞争锁
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

三、signalAll()方法

signalAll()方法用于将condition队列中所有node都转移到同步队列中。
这里分析其内部调用的 doSignalAll(Node first)方法。
        private void doSignalAll(Node first) {
            //移出整个condition队列中node,所以前后节点设为null
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                //挨个将node节点从condition队列移动到AQS阻塞队列,成功返回true。
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

四、总结

1、await()方法的调用线程必须持有锁,如果该线程通过重入锁,多次获取了锁,那么调用await(),会抛出异常 IllegalMonitorStateException,并且它在condition队列中的状态设为取消状态( CANCELLED),线程并不会被挂起。
2、signal()方法调用者必须持有锁,该方法并不能保证唤醒当前线程,只是将当前线程从condition队列转移到同步队列,仅当它在同步队列中的前驱节点被取消,或者等待状态未成功设置未signal状态,才会立即取消当前node的park状态。

你可能感兴趣的:(java)