• ReentrantLock和condition源码浅析(二)


     转载请注明出处。。。

    接着上一篇的ReentrantLock和condition源码浅析(一),这篇围绕着condition

    一、condition的介绍

    在这里为了作对比,引入Object类的两个方法,notify和wait方法,这两个方法作用,估计都很清楚,就是一个具有唤醒线程,另一个具有让线程等待的功能,适用场景,像类似生产者,消费者,那样。

    这两个方法的注意点,在于必须放在同步块中执行,具体原因可自行百度或谷歌。执行wait方法后,线程会阻塞,并释放同步代码块的锁(sleep方法会持有锁),notify的方法执行,会唤醒某个线程,但是如果有多个线程执行wait方法阻塞,notify的执行只会唤醒其中某个线程,并不能指定线程唤醒,这时要使用notifyAll才能达到唤醒所有阻塞线程。这样确实有点麻烦,而condition的引入就是为了解决只唤醒执行阻塞的线程。它具有超时作用,即超过某段时间,即会自动唤醒,不会造成一直阻塞。常用的阻塞队列就是这个类实现的。

    使用注意点,await和signal必须要在lock方法后执行,如果不执行lock方法就执行await或signal,会出现异常的,具体原因,稍后分析。

     二、condition的await方法

    在我们使用该方法时,首先会获取Lock接口的一个实现类,然后调用newCondition类方法,本文以ReentrantLock为例。使用方法如下

    1 Lock lock = new ReentrantLock();
    2 
    3 Condition empty = lock.newCondition();
    4 
    5 // 阻塞线程,并释放锁
    6 empty.await();
    7 
    8 //  唤醒前面阻塞的线程
    9 empty.signal();

     这里先从await方法(不带参数)开始,代码如下

    public final void await() throws InterruptedException {
                if (Thread.interrupted())
                    throw new InterruptedException();
                // 将当前线程包装成一个Node节点,并节点状态为condition,值为-2
                Node node = addConditionWaiter();
                // 释放当前线程持有的所有锁
                long savedState = fullyRelease(node);
                int interruptMode = 0;
                // 判断当前线程是否在同步队列中,即在head->tail队列中,如果不在,那就是还在等待队列中,阻塞当前线程。
                while (!isOnSyncQueue(node)) {
                    LockSupport.park(this);
                    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                        break;
                }
                // 当当前线程执行了signal方法会经过这个,即重新将当前线程加入同步队列中
                if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                    interruptMode = REINTERRUPT;
                if (node.nextWaiter != null) // clean up if cancelled
                    unlinkCancelledWaiters();
                if (interruptMode != 0)
                    reportInterruptAfterWait(interruptMode);
            }

    前面也说了该方法一定在lock方法内部执行,不然就会抛异常。具体代码在fullRelease(node)方法内,代码如下

     1 final long fullyRelease(Node node) {
     2         boolean failed = true;
     3         try {
     4             // 获取当前线程状态值,即持有了几个锁
     5             long savedState = getState();
     6            // 释放锁,最后最执行到ReentrantLock的tryRelease()方法,该段代码会判断当前线程是与持有锁的线程是同一个线程,如果不是,则抛异常
     7             if (release(savedState)) {
     8                 failed = false;
     9                 return savedState;
    10             } else {
    11                 throw new IllegalMonitorStateException();
    12             }
    13         } finally {
    14             if (failed)
    15                 // 抛异常了,则将此节点状态改为canceled,等待从队列中移除
    16                 node.waitStatus = Node.CANCELLED;
    17         }
    18     }

    该方法的执行,有这几个步骤

    1、将当前线程包装成一个node节点,且状态节点为condition,值为-2。

    2、释放当前线程持有的所有锁,让下一个线程能获取锁。

    3、如果条件满足,则阻塞该线程,等待被唤醒

    4、若被唤醒,则尝试加入该节点到同步队列中,直到获取锁。

    至于该方法的几个其他的重载方法,这里不过多叙述

    不一样的地方就是改造了while()循环内部代码,重点是使用了LockSupport.parkNanos方法能保证在规定时间,该线程会被唤醒。其他判断就是一些当前线程有没有被中断,有没有达到等待时间等。

    三、condition的signal方法

     该方法用来唤醒执行await方法被阻塞的线程,具体代码如下

     1 public final void signal() {
     2             // 与await方法一样,如果不在lock方法内执行,则也会抛异常
     3             if (!isHeldExclusively())
     4                 throw new IllegalMonitorStateException();
     5             Node first = firstWaiter;
     6             if (first != null)
     7                 // 唤醒等待队列中线程
     8                 doSignal(first);
     9 }
    10 
    11 private void doSignal(Node first) {
    12             do {
    13                 if ( (firstWaiter = first.nextWaiter) == null)
    14                     lastWaiter = null;
    15                 first.nextWaiter = null;
    16             } while (!transferForSignal(first) &&
    17                      (first = firstWaiter) != null);
    18 }
    19 
    20 final boolean transferForSignal(Node node) {
    21         /*
    22          * If cannot change waitStatus, the node has been cancelled. 如果改变node节点状态失败,即该节点被取消了
    23          */
    24         if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
    25             return false;
    26         // 将该节点加入同步队列中,即head->tail队列中
    27         Node p = enq(node);
    28         int ws = p.waitStatus;
    29         // 如果节点被取消,或更改状态失败,则唤醒被阻塞的线程
    30         if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
    31             LockSupport.unpark(node.thread);
    32         return true;
    33     }
    34     

     或许有的人会有疑问,执行await方法,有执行LockSupport.park方法来阻塞线程,但是执行signal方法顺利的话,没有代码执行LockSupport.unpark方法,那线程岂不是还一直在阻塞中?

    其实呢,signal起的作用并不是直接唤醒线程,它的作用是把阻塞的线程移到同步队列中,在上一篇中ReentrantLock和condition源码浅析(一) 博文中有介绍release方法,该方法内部有一个执行unpark方法,

    它会去不断的释放满足条件的并被阻塞的线程。

    ----------------------------------------------------------------------------------------华丽的分界线----------------------------------------------------------------------------------------------------------------------------

     以上内容就是我堆condition的了解,若有错误或不足之处,还望指正,谢谢!

  • 相关阅读:
    NOI2017 游戏
    2-SAT问题的方案输出
    hdu 2433 Travel
    bzoj千题计划230:bzoj3205: [Apio2013]机器人
    bzoj千题计划229:bzoj4424: Cf19E Fairy
    hdu 6166 Senior Pan
    poj 2404 Jogging Trails
    Oracle 删除数据后释放数据文件所占磁盘空间
    安装LINUX X86-64的10201出现链接ins_ctx.mk错误
    10G之后统计信息收集后为什么执行计划不会被立马淘汰
  • 原文地址:https://www.cnblogs.com/qm-article/p/10053619.html
Copyright © 2020-2023  润新知