ReentrantLock笔记(三) -- ConditionObject源码分析

ConditionObject源码分析

平时用ReentrantLock的时候,会用lock.newCondition()来创建一个条件,跟踪进去

    public Condition newCondition() {
        return sync.newCondition();
    }

    //Sync中的newCondition方法
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

ConditionObject是AbstractQueuedSynchronizer类中的一个内部类,
其继承关系如下
ConditionObject

看一下Condition接口定义的方法
condition

PS:首先要明确ConditionObject是在线程获得锁后,可以进行await释放资源进入睡眠并等待唤醒,signal则是唤醒同一个ConditionObject里的队列中头结点线程

从await方法入手

public final void await() throws InterruptedException {
       if (Thread.interrupted())//抛出中断异常
           throw new InterruptedException();
       Node node = addConditionWaiter();//添加一个节点到condition队列
       int savedState = fullyRelease(node);//释放资源
       int interruptMode = 0;
       while (!isOnSyncQueue(node)) {//保证节点已进入sync队列
           LockSupport.park(this);
           if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
               break;
       }
    //进入sync队列后开始请求资源
       if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
           interruptMode = REINTERRUPT;
       if (node.nextWaiter != null) //删除condition队列中已取消的节点
           unlinkCancelledWaiters();
       if (interruptMode != 0)//中断模式处理
           reportInterruptAfterWait(interruptMode);
   }

上面方法的逻辑是:
● await方法是能响应中断的,首先对线程是否中断进行判断
● 然后添加一个节点到condition队列
● 接着开始释放资源并返回资源数
● 再接着,如果节点不在在sync队列,休眠线程,线程被唤醒的时候进行中断检测,然后继续检查还在不在sync队列
● acquireQueued方法是开始请求获取锁,在上一篇文章已分析过,这个方法会返回线程在获得锁后是否中断,或者抛出异常。
● 当上面返回true,对interruptMode进行判断,不是THROW_IE(抛出异常)就设置成REINTERRUPT(重新设置中断标志)
● 如果condition队列中还有其他节点(node.nextWaiter != null),把节点状态不是CONDITION的从condition队列中移除
● 最后对中断模式进行不同处理

根据上面的逻辑一个个方法跟踪下去

先看插入一个节点到condition队列

private Node addConditionWaiter() {
       Node t = lastWaiter;//队列尾节点
       if (t != null && t.waitStatus != Node.CONDITION) {
           unlinkCancelledWaiters();//清理节点
           t = lastWaiter;//最终的尾节点
       }
    //生成新的等待节点,状态是CONDITION
       Node node = new Node(Thread.currentThread(), Node.CONDITION);
       if (t == null)//队列为空
           firstWaiter = node;
       else
           t.nextWaiter = node;//插到队尾
       lastWaiter = node;//刷新队尾节点
       return 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 {
            if (failed)//抛异常了
                node.waitStatus = Node.CANCELLED;
        }
    }

检查是否在sync队列,上面传进的节点状态为CONDITION,所以直接返回false了,进入睡眠了等待唤醒

    final boolean isOnSyncQueue(Node node) {
        //状态为CONDITION或node.prev为null直接返回false,因为处于sync队列中为获得锁的线程肯定有前驱节点
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null)//有后置节点那一定在sync队列
            return true;
        //如果node.prev不为空还不能确定就在sync队列,应为有可能CAS插入队列失败,所以还要遍历sync队列
        return findNodeFromTail(node);
    }

接着看线程被唤醒后,进行中断检测

    private int checkInterruptWhileWaiting(Node node) {
        return Thread.interrupted() ?
            (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
            0;
    }

    final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        //CAS设置节点状态从CONDITION变为0,说明是condition队列的节点,所以返回true要抛中断异常
            enq(node);//插入sync队列
            return true;
        }
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

可以看出线程如果等待过程中被设置了中断标志(注意:这里没有清除中断标志),就要看transferAfterCancelledWait方法的返回值

看condition队列调整的函数

    private void unlinkCancelledWaiters() {
        Node t = firstWaiter;//从头结点开始
        Node trail = null;
        while (t != null) {
            Node next = t.nextWaiter;
            if (t.waitStatus != Node.CONDITION) {
                //把不是CONDITION状态的移出队列
                t.nextWaiter = null;
                if (trail == null)
                    firstWaiter = next;
                else
                    trail.nextWaiter = next;
                if (next == null)//说明前任要被移出队列,刷新尾节点
                    lastWaiter = trail;
            }
            else
                trail = t;
            t = next;
        }
    }

中断处理比较简单,要么抛异常,要么重设中断标志,要么啥都不干

private void reportInterruptAfterWait(int interruptMode)
       throws InterruptedException {
       if (interruptMode == THROW_IE)
           throw new InterruptedException();
       else if (interruptMode == REINTERRUPT)
           selfInterrupt();
   }

await方法大致分析完,现在看signal方法

public final void signal() {
    //不是当前线程获得锁
       if (!isHeldExclusively())
           throw new IllegalMonitorStateException();
       Node first = firstWaiter;//取出condition队列的头结点进行唤醒
       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) {
       //无法CAS成功,说明该节点不是CONDITION状态了(被取消了)
       if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
           return false;
    //到这里说明节点状态是等待执行状态了
       //enq是插入sync队列,会返回sync队列前任尾节点(具体看前篇文章)
       Node p = enq(node);
       int ws = p.waitStatus;
       if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
           LockSupport.unpark(node.thread);
    //上面之所以唤醒node线程,是因为无法把它的前驱节点设置状态为SIGNAL
       return true;
   }

到这就基本分析完了,总结一下流程:

假设有ConditionObject为con,当前获得锁的线程 A 调用con的await方法,那当前线程就释放锁(全部资源),进入休眠,进入condition队列;当其他线程调用con的signal方法的时候,若此时的 A 已经是condition队列的头结点,那它就会被唤醒,进入sync队列并尝试获取和之前释放的同等数量的资源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值