Reentrantlock和其搭档Condition

这篇博客具体来说说Condition,但同时也离不开Reentrantlock,因为我们知道对于synchronize来说,是由Object类配合其进行使用,那么对于Reentrantlock来说则采用的Condition和其进行配合,但不同的是对于一个synchronize只拥有一个阻塞队列,就是我们平时new 一个Object对象,采用Object对象或者是一个Object类作为Synchronize的锁,但是不管是一个对象还是类来说,都只拥有一个阻塞队列。而对于Reentrantlock来说,一个Reentrantlock对象可以拥有多个由Reentrantlock 对象所生成的阻塞队列。

对于Condition来说只是一个接口,Condition的实现类是处于AQS中的内部类ConditionObject,
我在我的另一篇博客说过,AQS内部是维持一个等待队列的,这个等待队列是一个双向的链表,这个链表的节点是一个Node节点,再次来看一个这个Node节点。

Node节点中还有一些静态常量可以用来表示节点的状态
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;

下面只展示具体结构

static final class Node {volatile int waitStatus;volatile Node prev;volatile Node next;volatile Thread thread;Node nextWaiter;

其实在我的这篇 Reentrantlock和背后金主AQS 博客的最后,有一个图有Node节点,在哪个图中没有nextWaiter是因为AQS中所维持的等待队列中没有用到这个Node节点中的nextWaiter这个成员,所以就没有画出。
但是对于Condition来说,它的实现类ConditionObject这是在AQS中的内部类,Condition接口和Object来讲其实很相似,也提供了一些监视器的方法await() signal() 之类的。然后AQS中的ConditionObject就实现了Condition接口并且采用类自己的一套通知和等待机制。我们平常配合Lock使用的也是ConditionObject这个类。

我们都知道,当多个线程想要获取同一个锁的时候,其他线程会被方法lock的等待队列中,同时也是一个AQS所维持的等待队列,但当出现condition.await() 或者condition.signal的时候。我们知道condition.await() condition.signal相当于我们见过的object.wait object.notifiy();
condition.await() 将运行当前代码的线程加入到阻塞队列中,condition.signal采用运行当前的代码的线程唤醒加入到阻塞队列中的线程,不管时对于condition和Object来说,唤醒的阻塞队列必须是同一condition对象或者是Object对象或者是Object类下的阻塞队列。这是我们知道的。
下面来说说ConditionObject实现Condition接口所维持的阻塞队列和相应的方法。

ConditionObjec中await()方法:
public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();//生成一个Node,这个Node就是加入到一个ConditionObject对象所维持的阻塞队列中。//阻塞队列是一个不带头节点的单向链表//对于阻塞队列,虽然和等待队列采用的是一个结构Node,但是对于成员的使用却不同。//阻塞队列中Node没有使用到volatile Node prev,volatile Node next;这两个成员,而使用的是Node nextWaiter,所以说阻塞队列是一个单向链表。//addConditionWaiter()方法生成一个阻塞节点,并且将这个节点的状态设置为Node.CONDITION(-2)这是Node中的静态常量,从上面的Node节点我们知道节点状态采用的是int类型来表示,相当于说明这个节点为阻塞状态。并且将这个节点链入到阻塞队列的尾部。如果是第一个阻塞节点,则充当头节点。Node node = addConditionWaiter();//fullyRelease()这个方法先获取锁的状态,因为之前说过一个conditionObject对象的生成是由一个lock生成的,而一个lock又对应一个AQS,//所以获取锁的状态当然是,conditionObject对象对应的lock的状态,也就是当前AQS的状态。//因为调用了await方法,所以当前线程要释放当前持有的锁,//fullyRelease()先释放当前的锁,在释放锁的同时会唤醒由于没有想获取锁而没有获取的在等待队列中的等待节点。只要锁释放,必唤醒等待队列中等待最久的等待节点。int savedState = fullyRelease(node);int interruptMode = 0;// 判断将当前线程所形成的阻塞节点是否已经被从阻塞队列移到锁的等待队列中// isOnSyncQueue(node)中首先判断节点的状态是否是Node.CONDITION(-2),如果是则返回false//显然不管哪个线程被阻塞一开始该线程所形成的阻塞节点的状态都是-2//所以循环程序会进入,然后当前线程被真正阻塞。而在这之前,也已经将该线程所构成的阻塞节点加入到阻塞队列中了。while (!isOnSyncQueue(node)) {LockSupport.park(this);//阻塞当前线程if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//判断当前线程是否设置类中断标志,break;}//当当前线程被唤醒后,则会将当前线程在阻塞队列中的等待节点的状态改为0,//唤醒不等于立马就被执行,而是将包装该线程的阻塞节点加入的conditionObject对象对应的锁的等待队列中,在阻塞队列中删除该节点//等等待队列中该线程所包装的节点有机会被从等待队列里唤醒,说明该线程真正执行了,该线程才接着上边循环中的 LockSupport.park(this)继续执行//此时因为该线程包装的节点的状态已经从-2改为0了,不再满足isOnSyncQueue(node)方法中第一个判断,那么会通过该方法中的其他判断返回true//所以会跳出上边的循环到下边的代码。if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//因为该线程开始执行,所以在acquireQueued(node, savedState) 重新持有锁,并且将该线程包装的等待节点在等待队列中进行删除//acquireQueued(node, savedState)方法再成功持有锁的情况下,返回值为false;interruptMode = REINTERRUPT;if (node.nextWaiter != null)//判断该节点是否还在阻塞队列里unlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);}
ConditionObjectawait(long TimeUnit)方法

对于这个方法来说不同于单纯的await(), await()方法将当前线程阻塞,需要别的线程进行signal()当前线程才可以继续往下执行。
而await(long TimeUnit)不需要别的线程来进行signal操作进行唤醒。
await(long TimeUnit)
这个方法和await()方法前半部分和后半部分几乎一样,只是中间有不一样的地方

 public final boolean await(long time, TimeUnit unit)throws InterruptedException {//将time转化为纳秒进行使用。long nanosTimeout = unit.toNanos(time);if (Thread.interrupted())//当前线程是否设置中断标志throw new InterruptedException();//将当前线程包装成一个阻塞节点,并将节点的状态设置为Node.CONDITION(-2)Node node = addConditionWaiter();//在阻塞当前线程的时候释放当前线程持有锁,并且在释放锁的时候唤醒AQS等待队列中等待最久的节点。int savedState = fullyRelease(node);//根据当前时间是等待时间计算截止时间final long deadline = System.nanoTime() + nanosTimeout;boolean timedout = false;int interruptMode = 0;//这个循环中的内容就是和await方法中循环不一样的地方。//因为当前线程所包装的阻塞节点的状态应在此之前设置为-2,所以满足isOnSyncQueue(node)方法的第一个判断,返回false//不管哪个线程进行调用await(long time, TimeUnit unit),都会先进入这个while循环,因为在调用时都会将线程包装成节点,并且状态被设置为-2(CONDITION)阻塞状态while (!isOnSyncQueue(node)) {if (nanosTimeout <= 0L) {//如果当前等待时间小于0,表明不需要等待//直接将该阻塞节点的状态改为0,并且加入到AQS的等待队列中。//跳出循环timedout = transferAfterCancelledWait(node);break;}//如果大于1000L,为默认的时间//那么会将该线程进行阻塞,并且此时该节点已经加入到阻塞队列中了,//等待被唤醒。     if (nanosTimeout >= spinForTimeoutThreshold)LockSupport.parkNanos(this, nanosTimeout);if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;nanosTimeout = deadline - System.nanoTime();}if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null)unlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);return !timedout;}
ConditionObject的signal()方法:

线程调用signal方法只能唤醒阻塞队列中的最先被阻塞的线程。同时也将阻塞队列中唤醒的线程所包装的节点的状态从阻塞态变为0,在阻塞队列中删除该节点,并将该节点加入到该conditionObject对应lock的AQS的等待队列中,被唤醒的线程不是立即就被执行,而需要在等待队列中等待,等到机会执行,并且重新在持有锁。

public final void signal() {if (!isHeldExclusively())throw new IllegalMonitorStateException();//得到ConditionObject中阻塞队列的首节点Node first = firstWaiter;if (first != null)doSignal(first);}
private void doSignal(Node first) {do {if ( (firstWaiter = first.nextWaiter) == null)//如果阻塞队列中只有一个节点,那无疑删除该节点后//队列的首节点和末节点都为nulllastWaiter = null;//如果首节点还存在后驱节点,那个该节点删除后,后驱节点必为首节点//此时的这几部操作已经将将要唤醒的阻塞节点从阻塞队列中进行删除了first.nextWaiter = null;} while (!transferForSignal(first) &&(first = firstWaiter) != null);}
final boolean transferForSignal(Node node) {//将将要唤醒的阻塞节点的状态从Node.CONDITION该为0,表明该节点不再为阻塞态,if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))return false;//enq(node)将已经删除的阻塞节点加入到该conditionObject所在lock的AQS的等待队列中,//因为lock锁的等待队里的末节点的状态始终为0//enq(node)的返回值为等待队列的末节点,因为阻塞节点将要加入到等待队列中,//所以对于将要加入的阻塞节点来说,它的前驱节点就为等待对类中的末节点。  //enq(node)返回的是参数节点的前驱节点。       Node p = enq(node);//那么对于p的状态来说为0int ws = p.waitStatus;//对于给等待队列加入节点,就将加入节点的前驱节点的状态设置为SIGNAL,一个节点的前驱节点状态设置为SIGNAL,那么该节点才会再等待队列中,被唤醒(这里说的唤醒是对于某线程持有锁,当释放锁的时候就会唤醒等待队列中的线程)if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))LockSupport.unpark(node.thread);return true;
ConditionObject的signalAll()方法

.signal() -> doSignal ->transferForSignal;
.signalAll()->doSignalAll->transferForSignal;

唯一不同在于ConditionObject.signalAll()调用的doSignalAll()挨个唤醒阻塞队列中的节点,同样也将阻塞队列中的阻塞节点全部删除。

 private void doSignalAll(Node first) {//这第一句就已经很说明问题了,直接将首节点的值为末节点的值都赋值为nulllastWaiter = firstWaiter = null;do {//采用的是将头节点删除,然后将头节点的后驱节点作为头节点,然后依次删除头节点Node next = first.nextWaiter;first.nextWaiter = null;transferForSignal(first);first = next;} while (first != null);}

Reentrantlock和其搭档Condition —————— 开开开山怪相关推荐

  1. Java多线程11:ReentrantLock的使用和Condition

    ReentrantLock ReentrantLock,一个可重入的互斥锁,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. Reentran ...

  2. Java多线程(九)之ReentrantLock与Condition

    一.ReentrantLock 类 1.1 什么是reentrantlock java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java ...

  3. 并发编程 Java 三把锁(Synchronized、ReentrantLock、ReadWriteLock)

    Synchronized synchronized 的 3 种用法: 指定加锁对象(代码块):对给定对象加锁,进入同步代码前要获得给定对象的锁. void resource1() {synchroni ...

  4. 【java线程】锁机制:synchronized、Lock、Condition

    [Java线程]锁机制:synchronized.Lock.Condition 原创 2013年08月14日 17:15:55 标签:Java /多线程 74967 http://www.infoq. ...

  5. ReentrantLock与synchronized

    1.ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的 ...

  6. Java ReentrantLock 详解

    ReentrantLock 和 synchronized 的区别: 1.ReentrantLock 不需要代码块,在开始的时候lock,在结束的时候unlock 就可以了. 但是要注意,lock 是有 ...

  7. java lock condition_Java 通过 Lock 和 竞争条件 Condition 实现生产者消费者模式

    更多 Java 并发编程方面的文章,请参见文集<Java 并发编程> 竞争条件 多个线程共享对某些变量的访问,其最后结果取决于哪个线程偶然在竞争中获胜. condition.await() ...

  8. 重入锁:ReentrantLock 详解

    在JDK5.0版本之前,重入锁的性能远远好于synchronized关键字,JDK6.0版本之后synchronized 得到了大量的优化,二者性能也不分伯仲,但是重入锁是可以完全替代synchron ...

  9. Java锁详解之ReentrantLock

    文章目录 写在前面 ReentrantLock的重要方法 ReentrantLock使用示例 ReentrantLock的公平和非公平锁 ReentrantLock的重入锁 ReentrantLock ...

  10. Lock接口Condition,以及Lock与synchronized异同

    一.synchronized与Lock 基于synchronized关键字去实现同步的,(jvm内置锁,也叫隐式锁,由我们的jvm自动去加锁跟解锁的)juc下的基于Lock接口的这样的一种锁的实现方式 ...

最新文章

  1. rust怎么用items刷东西_装修贷卡怎么刷?不同银行的用不同的POS机刷才对
  2. swift 中showAlertTitle的使用
  3. 23. C# -- 封装
  4. 0220互联网新闻 | 豆瓣FM获腾讯音乐娱乐集团投资;华为云将在新加坡拓展云平台和人工智能能力...
  5. 剑指offer——面试题10:二进制中1的个数
  6. Linux之判断socket是否断开
  7. j$(function() j$(document).ready 区别
  8. 实时操作系统分类、特点及实现原理
  9. SpringBoot框架理解
  10. 浅谈NFC、RFID、红外、蓝牙的区别
  11. Codesys和基恩士扫码枪Ethernet/IP通信
  12. LM358恒流恒压原理
  13. Kotlin 1.4 版本正式发布:新功能一覽
  14. 电脑 蓝屏 问题签名: 问题事件名称: BlueScreen OS 版本: 6.1.7600.2.0.0.256.1 区域设置 ID: 2052...
  15. mysql锁机制和索引_MySql锁机制及索引 · 十年饮冰,难凉热血
  16. win11桌面改成win7桌面的设置方法
  17. Yura2020年限量高级定制版阅读进度表
  18. Spring 学习 (三)大话AOP
  19. 计算机课件教程星空,PPT怎么绘制宇宙的另一边思维导图
  20. 怎样做小游戏挖金子(VC,源码2)

热门文章

  1. Surface Pro 4 系统优化全教程及QA
  2. 谷歌及360浏览器插件制作
  3. LoRa和NB-IoT会长期共存吗?
  4. 肥猫吃披萨游戏JAVA,题解 P1488 【肥猫的游戏】
  5. Qt编写水波进度条控件
  6. OpenCV情绪识别(Python实现,超简单)
  7. 《科技创业启示录》一第1章 乔斯·怀特
  8. 实现内网穿透的两种方式
  9. 项目验收之时,所需文档
  10. 更换固态硬盘和机械硬盘以及重装系统