Reentrantlock和其搭档Condition —————— 开开开山怪

2024-02-07 07:48

本文主要是介绍Reentrantlock和其搭档Condition —————— 开开开山怪,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 —————— 开开开山怪的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/687025

相关文章

【Spring Boot】 SpringBoot自动装配-Condition

目录 一、前言二、 定义2.1 @Conditional2.2 Condition2.2.1 ConditionContext 三、 使用说明3.1 创建项目3.1.1 导入依赖3.1.2 添加配置信息3.1.3 创建User类3.1.4 创建条件实现类3.1.5 修改启动类 3.2 测试3.2.1 当user.enable=false3.2.2 当user.enable=true 3.3

Java并发:互斥锁,读写锁,Condition,StampedLock

3,Lock与Condition 3.1,互斥锁 3.1.1,可重入锁 锁的可重入性(Reentrant Locking)是指在同一个线程中,已经获取锁的线程可以再次获取该锁而不会导致死锁。这种特性允许线程在持有锁的情况下,可以递归地调用自身的同步方法或代码块,而不会因为再次尝试获取相同的锁而被阻塞。显然,通常的锁都要设计成可重入的。否则就会发生死锁。 synchronized关键字,就是

【硬刚Java并发】JUC基础(七):Condition 控制线程通信

本文是对《【硬刚大数据之学习路线篇】从零到大数据专家的学习指南(全面升级版)》的Java并发部分补充。 Condition Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的

java线程 yield,sleep,join,synchronized wait notify notifyAll,ReentrantLock lock condition, 生产者消费者

yield,sleep,join yield,join,sleep,join是Thread中的方法,不需要 在synchronized 代码块中调用,和synchronized 没关系,也不会释放锁。 Thread.sleep(100);Thread.yield();Thread t;t.join(); (1)yield()不一定保证让出cpu yield()只是使当前线程重新回

synchronized wait()/notify 对比 ReentrantLock await()/signal()

结论 synchronized synchronized 配合 wait()/notify 无法实现精准唤醒线程 ReentrantLock ReentrantLock 配合 Condition await()/signal() 可以实现精准唤醒线程 (指唤醒指定的线程) ReentrantLock 如何实现精准唤醒线程 一个 lock 配合多个 Condition, 且

ReentrantLock的lockInterruptibly()理解

ReentrantLock锁有几种:lock、tryLock、tryLock(long timeout, TimeUnit unit)、lockInterruptibly。 lock 阻塞等待获取锁,优先考虑获取锁,待获取锁成功后,才响应中断。 lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。  tryLock() 是一个有返回值的方法,试图申请一个锁

【并发】Lock与ReentrantLock

1 Lock基本使用 Lock能实现代码同步,它比synchronized更具灵活性,什么时候锁住,什么时候释放锁等都是看得见的,使用时必须使用try{}finally{},意思是万一发生异常或者错误都可以释放锁。 try{}finally{//释放锁} 使用示例 public class SaleTicket implements Runnable {private int tic

Java 入门指南:Java 并发编程 —— Condition 灵活管理线程间的同步

Condition Condition 是 Java 并发编程中的一种高级同步工具,它可以协助线程之间进行等待和通信。提供了一种比传统的 wait() 和 notify() 更加灵活的方式来管理线程间的同步。Condition 接口通常与 Lock 接口一起使用,允许更细粒度的控制线程的等待和唤醒。 每个 Condition 对象都与一个 Lock 对象相关联,它可以在等待之前获取到锁,并在等

Adobe Illustrator vs Photoshop:设计界的“相声搭档”

在这个五彩斑斓的设计世界里,Adobe Illustrator(简称AI)和Adobe Photoshop(简称PS)就像是相声界的黄金搭档,一个逗哏,一个捧哏,共同演绎着创意的无限可能。今天,咱们就来一场轻松幽默的对比分析,看看这对“相声搭档”如何在设计舞台上各展所长,又如何在某些时候“相爱相杀”。 开场白:谁是“颜值担当”? 首先,得说说这对搭档的“颜值”。Photoshop,这位“图像处