美文网首页
jdk1.8锁的condition的源码学习

jdk1.8锁的condition的源码学习

作者: 他们叫我小白 | 来源:发表于2017-04-04 18:59 被阅读0次

    Condition是新的锁的用于阻塞和唤醒的的,这里面创建了 一个ConditionObject对象,这个对象继承自Condition

    这个对象持有2个notd对象,第一个是第一个condition队列的node对象,第二个是这个阻塞对象的最后一个对象。

    这个Condition总共有下面这几个方法,这里先不剧透了,一个个的来学

    await()方法,这个方法在AQS里实现的,首先进来就判断这个线程是否被中断,如果中断则直接抛出异常,然后调用一个addConditionWaiter方法。这个方法的主要功能是包装当前线程为一个Node,然后加入到阻塞队列中去,然后把包装的node返回来。然后调用fullyRelease方法,并且传入我们刚刚得到的node。

    在addConditionWaiter这个方法里面首先拿到尾阻塞node,如果尾node不为null,且等待状态不是node.condition,则调用unlinkCancelledWaiters方法。一般第一个await进来获取到的尾节点是Null,所以第一个条件不满足,然后直接当前线程以及阻塞状态保证为一个新node,如果这个尾节点为null则直接把我们的node设置为头节点,如果不为Null则把尾节点的下一个节点设置为新建的node,最后把我们新建的node指向尾节点,然后返回新node。

    在node对象里面申明了几个对象,第一个CANCELLED代表着线程已经取消,SIGNAL代表着需要解除寄存的线程node,也就是要抢到锁的node,condition代表着阻塞等待的状态。

    在fullyRelease方法里首先初始化一个失败的布尔值为true,然后先调用getState方法获取当前node的状态。然后调用release方法,并且把获取到的状态传进去,其实也就是锁次数,如果非重入则是1,如果多次重入则代表锁定多次,这样不管锁定多少次都会一次释放。

    在release方法里首先去调用TryRelease方法,这个方法才是真正释放线程锁的功能实现。如果成功了,则先拿到我们的头节点,然后去调用unPark方法释放阻塞等待抢锁的对象。

    这个方法里会对锁定次数进行减操作,然后通过把排他线程设置为null来释放锁,最后把持有锁的状态设置为0.

    最后我们返回了释放锁的锁定次数。最后如果释放失败则把线程node状态修改为CANCELLED

    为了方便我们在把前面的代码贴出来,这里返回次数后,然后先初始化一个变量,然后调用isOnSyncQueue方法并且把新的node传进去

    这个方法里面首先判断node的等待状态是不是CONDITION,很显然我们前面在初始化的时候设置的状态就是这个,即使并发下修改掉了,我们依旧可以根据这个节点的前节点是不是null,如果是null则代表成功,如果不是那说明这个线程node又进入了阻塞队列中去了,如果上面条件不满足则判断这个node后面有没有对象,如果有则说明还在队列中。如果没有则最后调用一个

    这个方法里面同样判断尾节点是不是当前node,如果是的话说明还在队列中,如果尾节点是null则说明不是,最后然后前移一位继续找。最终目的只有一个,判断这个节点是不是在阻塞队列中。

    这里判断如果不在等待队列中则调用LockSupport.park命令直接寄存该线程,等待释放。这里如果别的调用了释放命令后,线程继续执行,然后调用checkInterruptWhileWaiting方法

    这个方法里面首先判断是否被中断,如果被中断了则继续调用transferAfterCancelledWait方法,

    在这个方法里面就敢了一件事,把当前阻塞等待的状态变更为0,然后调用一个enq的方法

    在这个方法里,用一个for循环来首先拿到尾节点,如果尾节点为空,说明我们是空的阻塞队列,然后就先初始化一个Null的node,然后把这个新的Node设置为尾Node。然后与null的头node建立双向关联关系。

    然后根据不同的状态返回是THROW_IE或者是REINTERRUPT,或者是0.如果不是0则直接用break结束线程,如果是0则继续向下执行。然后调用acquireQueued方法,传入当前node和线程锁次数。如果再次尝试获取锁成功则返回false,如果失败则返回true,则继续判断中断模式是否是THROW_ID,如果不是则把中断模式设置为重新打断。如果不是则继续判断节点的下一个阻塞等待位不为null,如果不是null则调用unlinkCancelledWaiters方法,

    acquireQueued方法里面再次尝试去获取锁,如果获取成功则返回false,如果获取失败或者线程被寄存然后又唤醒后则返回true。

    unlinkCancelledWaiters方法里首先从头阻塞节点开始,在for循环里,如果t不是空则取到它的下一个节点,然后判断t节点的状态,如果是等待阻塞状态则把trail指向这个头节点t,然后t向下移一位,也是通过一个遍历链条的方式直到t为null时候结束循环。这里也就是把取消的那里线程节点给解除掉阻塞链中。

    这里我们看最后一个判断,判断打断状态如果不是0,则调用reportInterruptAfterWait方法

    这个方法里面判断这个打断状态如果是THROW_IE则抛出异常结束线程,如果是REINTERRUPT则调用selfInterrupt方法

    这个方法就是自己结束当前线程。

    整个阻塞方法的核心在LockSupport.park(this);当别的线程调用了unpark方法,则线程就被唤醒了。

    addConditionWaiter方法是构造阻塞链的核心。把所有新的阻塞线程都加入到队列尾部。这里与lock的阻塞是不同的,lock的阻塞是通过node的next来构造阻塞竞争链的,而await方法的通过nextwaiter实现的。

    signal()方法主要是用作唤醒线程的。之前我描述过原有的wait和notify方法。我们来看看signal是不是和notify一样按照入队的方式唤醒的。

    第一步进来先调用了一个isHeldExclusively,就是判断获取到锁的线程是不是当前线程,如果不是则抛出异常。,如果是则首先获取到阻塞队列中的第一个node,然后如果不为Null,则执行doSignal方法

    这个方法里面,首先看下一个节点是不是null,如果是null则把最后一个节点也置为null,如果下一个节点不是null,则把传入的节点的next设置为null,然后调用transferForSignal方法

    这个方法里面就是把这个节点的阻塞状态变更为0。如果失败则直接返回false,如果成功则继续向下执行

    通过调用enq来构造阻塞链然后返回,然后获取到这个节点的状态,如果这个状态>0说明非阻塞状态,然后调用unpark方法就真正的唤醒了阻塞的线程了。

    由此可以看到,Signal方法是按照阻塞队列的顺序严格一致的唤醒的,压根不是随机唤醒。

    signlAll方法是一次唤醒所有,同样是也是先判断线程一致性。然后调用了doSignalAll方法,传入阻塞的头节点

    这个方法里面首先将头尾节点都设置为null,然后从我们传入的头节点开始设置下一个节点为null,然后调用transferForSignal方法释放这个节点的阻塞状态,然后在移动到下一个阻塞节点,如此循环直到所有节点释放为止。

    由此可见,singinAll方法唤醒也是依次从阻塞的头节点开始然后挨个唤醒,所以,千万别说是无序随机的,这个依赖于线程进入阻塞队列的顺序。

    下面我们看带时间参数的await方法,其实和await方法差不多,也许先通过addConditionWaiter添加到阻塞链中去

    这里调用的parkNanos定时寄存的,也就是指定阻塞时间,时间到了 后自动唤醒。

    awaitNanos方法需要传入一个纳秒时间参数,其他代码与上面的阻塞代码差不多,也是通过parkNaos阻塞的

    awaitUntil方法需要传入一个时间日期

    其他代码基本不变,主要是这里先把我们的date转化为毫秒数,然后判断当前时间与设定时间大小,如果设定时间大则使用的是parkUntil执行寄存。

    awaitUninterruptibly这个方法首先添加到阻塞队列中去,然后同样调用park寄存,最后把interrupted如果线程被中断过则改为true,最后去尝试获取锁,获取成功后判断interrupted,如果为true则执行自销毁。这个方法的目的也就是对于那些执行过中断的线程会执行自行销毁,只有没有执行过打断的线程才会真正执行完。

    至此我们condition也就学完了。

    相关文章

      网友评论

          本文标题:jdk1.8锁的condition的源码学习

          本文链接:https://www.haomeiwen.com/subject/ekxzottx.html