• 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的了解,若有错误或不足之处,还望指正,谢谢!

  • 相关阅读:
    MSSQL大量数据时,建立索引或添加字段后保存更改超时该这么办
    POJ 3261 Milk Patterns (后缀数组)
    POJ 1743 Musical Theme (后缀数组)
    HDU 1496 Equations (HASH)
    694. Distinct Substrings (后缀数组)
    POJ 1222 EXTENDED LIGHTS OUT (枚举 或者 高斯消元)
    POJ 1681· Painter's Problem (位压缩 或 高斯消元)
    POJ 1054 The Troublesome Frog (hash散列)
    HDU 1716 排列2
    HDU 4405 Aeroplane chess (概率DP & 期望)
  • 原文地址:https://www.cnblogs.com/qm-article/p/10053619.html
Copyright © 2020-2023  润新知