条件锁

ReentrantLock类有一个方法newCondition用来生成这个锁对象的一个条件(ConditionObject)对象,它实现了Condition接口。

Condition提供了线程通讯的一套机制await和signal等线程间进行通讯的方法。。


1、适用场景
     当某线程获取了锁对象,但由于某些条件没有满足,须要在这个条件上等待,直到条件满足才可以往下继续运行时。就须要用到条件锁。

     这样的情况下,线程主动在某条件上堵塞,当其他线程发现条件发生变化时,就能够唤醒堵塞在此条件上的线程。

2、使用演示样例
     以下是来自JDK的一段演示样例代码,须要先获得某个锁对象之后,才干调用这个锁的条件对象进行堵塞。

     
class BoundedBuffer {final Lock lock = new ReentrantLock();final Condition notFull  = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100];int putptr, takeptr, count;public void put(Object x) throws InterruptedException {lock.lock(); try {while (count == items.length)notFull.await();items[putptr] = x;if (++putptr == items.length) putptr = 0;++count;notEmpty.signal();} finally {lock.unlock();}}public Object take() throws InterruptedException {lock.lock();try {while (count == 0)notEmpty.await();Object x = items[takeptr];if (++takeptr == items.length) takeptr = 0;--count;notFull.signal();return x;} finally {lock.unlock();}}}
注意上面的代码,先是通过lock.lock获得了锁对象,然后发现条件不满足时(count==items.length),缓存已满,无法继续往里面写入数据,这时候就调用条件对象notFull.await()进行堵塞。

假设条件满足,就会往缓存中写入数据,同一时候通知等待缓存非空的线程,notEmpty.signal.

这样就实现了读线程和写线程之间的通讯

3、线程堵塞对锁的影响
     上面的样例中。线程是先获得了锁对象之后。然后调用notFull.await进行的线程堵塞。在这样的情况下,拥有锁的线程进入堵塞,是否可能会造成死锁。
     
     答案当然是否定的。

由于线程在调用条件对象的await方法中,首先会释放当前的锁,然后才让自己进入堵塞状态,等待唤醒。


4、线程的条件等待、唤醒与锁对象的关系
     在ReentrantLock解析中说过。AbstractQueuedSynchronizer的内部维护了一个队列,等待该锁的线程是在这个队列中。类似的,ConditionObject内部也是维护了一个队列,等待该条件的线程也构成了一个队列。

     当现成调用await进入堵塞时。便会增加到ConditionObject内部的等待队列中。

注意,这里是自动进入堵塞。除非被其他线程唤醒或者被中断,否则线程将一直堵塞下去。


     当其他线程调用signal唤醒堵塞的线程时,便把等待队列中的第一个节点从队列中移除,同一时候把节点增加到AbstractQueuedSynchronizer 锁对象内的等待队列中。为什么是进入到锁的等待队列中?由于线程被唤醒之后,并不意味着就能立马运行。

此时,其他线程有可能正好拥有这个锁,前面也已经有现成在等待这个锁,所以被唤醒的线程须要进入锁的等待队列中,在前面的线程运行完毕后,才干继续兴许的操作。


     可參考下图
     
     
5、线程能否同一时候处于条件对象的等待队列中和锁对象的等待队列中

     不能。

线程仅仅有调用条件对象的await方法,才干进入这个条件对象的等待队列中。而线程在调用await方法的前提是线程已经获取了锁,所以线程是在拥有锁的状态下进入条件对象的等待队列的。拥有锁的线程也就是正在执行的线程,是不在锁对象的等待队列中的。

     仅仅有当一个线程试着获取锁的时候。而这个锁正好又由其他线程占领的时候。线程才会进入锁的等待队列中,等待拥有锁的线程运行完毕。释放锁的时候被唤醒。

6、实现原理

     相关代码在AbstractQueuedSynchronizer的内部类ConditionObject中能够看到。

     ConditionObject有两个属性firstWaiter和lastWaiter,分别指向的是这个条件对象等待队列的头和尾。
     队列的各个节点都是Node(AbstractQueuedSynchronizer的内部类)对象,通过Node对象的nextWaiter之间进行向下传递,所以,条件对象的等待队列是一个单向链表。


以下是await的源码
       public final void await () throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport. park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

首先是调用addConditionWaiter把当前线程增加到条件对象的等待队列中,然后fullyRelease来释放锁,然后通过isOnSyncQueue来检查当前线程节点是否在锁对象的等待队列中。
为什么要做这个检查?由于线程被signal唤醒的时候,是首先增加到锁对象的等待队列中的。

假设没有在锁对象的等待队列中,那么说明事件还没有发生(也就是没有signal方法没有被调用)。所以线程须要堵塞来等待被唤醒。


在addConditionWaiter方法中完毕了等待队列的构建过程,代码例如以下

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node. CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node. CONDITION);
            if (t == null )
                firstWaiter = node;
            else
                t. nextWaiter = node;
            lastWaiter = node;
            return node;
        }
线程增加队列的顺序与增加的时间一致,刚增加的线程是在队列的最后面。

以下来看线程的唤醒
 public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

唤醒操作实际上是通过doSignal完毕。注意这里传递的是firstWaiter指向的节点,也就是唤醒的时候,是从队列头開始唤醒的。
从尾部进入,从头部唤醒。所以这里的等待队列是一个FIFO队列。


private void doSignal (Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null ;
                first. nextWaiter = null ;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null );
        }

doSignal方法把第一个节点从条件对象的等待队列中移除,然后终于是走到transferForSignal中来进行操作。
final boolean transferForSignal (Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node. CONDITION, 0))
            return false ;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus ;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node. SIGNAL))
            LockSupport. unpark(node.thread);
        return true ;
    }

通过enq方法,把线程所在的节点增加到锁对象的等待队列中,这样在条件合适的时候,线程被唤醒,获得锁,然后运行。

转载于:https://www.cnblogs.com/liguangsunls/p/7360190.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/282713.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机应用技术 平面设计,全国信息化计算机应用技术水平教育考试试卷 平面设计师...

科目编号:4233全国信息化计算机应用技术水平教育考试试卷(考试时间:180分钟 考试总分:100分 专业认证课程:Photoshop 平面设计)注意事项1、 请首先按要求在试卷的标封处填写您的姓名、考号等;2、 请仔细阅读各种题目的…

RabbitMQ之消息模式简单易懂,超详细分享

前言上一篇对RabbitMQ的流程和相关的理论进行初步的概述,如果小伙伴之前对消息队列不是很了解,那么在看理论时会有些困惑,这里以消息模式为切入点,结合理论细节和代码实践的方式一起来学习。正文常用的模式有Simple、Work、Fanout…

jvm系列(八):jvm知识点总览

在江湖中要练就绝世武功必须内外兼备,精妙的招式和深厚的内功,武功的基础是内功。对于武功低(就像江南七怪)的人,招式更重要,因为他们不能靠内功直接去伤人,只能靠招式,利刃上优势来…

计算机基础知识的文献,四 计算机文献检索基础知识(原理、结构和功能)

1.计算机检索原理计算机一方面接受用户的检索提问,一方面从数据库中读取文献记录,然后把两者进行比较,即检索提问标识与文献记录标识进行匹配运算,如果比较的结果一致,那么这篇文献就会作为命中文献在检索结果中显示&a…

C# 基于.NET6的CM+Fody+HC入门实战项目(经典)

概述上期我们概述了CMFodyHC,如果之前没有阅读,可以先了解下:C# 为什么说CMFodyHC是WPF开发的最强组合?今天基于最新的VS版本、最新的CM框架版本,.NET基于6.0,搭建了一个WPF入门学习项目实例,关…

Kong入门学习实践(2)实验环境搭建

【API网关】| 总结/Edison Zhou最近在学习Kong网关,因此根据老习惯,我会将我的学习过程记录下来,一来体系化整理,二来作为笔记供将来翻看。由于我司会直接使用Kong企业版,学习过程中我会使用Kong开源版。本篇&#xff…

我的未来计算机作文,我的未来作文(精选4篇)

我的未来作文(精选4篇)在平平淡淡的日常中,大家总免不了要接触或使用作文吧,作文根据体裁的不同可以分为记叙文、说明文、应用文、议论文。怎么写作文才能避免踩雷呢?以下是小编收集整理的我的未来作文,仅供参考,大家一…

RDS for MySQL Mysqldump常见问题及处理

2019独角兽企业重金招聘Python工程师标准>>> 摘要: RDS for MySQL Mysqldump 常见问题和处理 GTID 特性相关 避免表级锁等待 设置导出字符集 其他导出时需要注意的选项 举例 RDS for MySQL 不支持的选项 RDS for MySQL 逻辑备份 1. GTID 特性相关 MySQ…

《ASP.NET Core 6框架揭秘》实例演示[27]:ASP.NET Core 6 Minimal API的模拟实现

Minimal API仅仅是在基于IHost/IHostBuilder的服务承载系统上作了小小的封装而已,它利用WebApplication和WebApplicationBuilder这两个类型提供了更加简洁的API,同时提供了与现有API的兼容。[本文节选《ASP.NET Core 6框架揭秘》第17章]一、基础模型二、…

高性能Server---Reactor模型

无处不在的C/S架构 在这个充斥着云的时代,我们使用的软件可以说99%都是C/S架构的! 你发邮件用的Outlook,Foxmail等你看视频用的优酷,土豆等你写文档用的Office365,googleDoc,Evernote等你浏览网页用的IE,Chrome等(B/S是特殊的C/S)……C/S架构…

springmvc_3(将数据放入map中)

jsp页面 结果 转载于:https://www.cnblogs.com/mohehpc/p/6491376.html

聊聊策略模式

1、简介策略模式就是把各个平等的具体实现进行抽象、封装成为独立的算法类,然后通过上下文和具体的算法类来进行交互。各个策略算法都是平等的,地位是一样的,正是由于各个算法的平等性,所以它们才是可以相互替换的。虽然我们可以动…

张旭升20162329 2006-2007-2 《Java程序设计》第一周学习总结

20162329 2006-2007-2 《Java程序设计》第一周学习总结 教材学习内容总结 通过打书上的代码熟悉了Java编程的基本过程 教材学习中的问题和解决过程 1.因为我的虚拟机不可用所以我在Windows中安装了bash和git,但是由于Windows下bash中没有中文而且我英语又不是很好所…

《图解 HTTP》读书笔记(未完待续)

ARP 协议(Address Resolution Protocol)一种以解析地址的协议,根据通信双方的 IP 地址就可以查出对应的 MAC 地址。MAC( Media Access Control Address)地址是指网卡所属的固定的地址MIME,多部分对象集合&a…

.NET 实现启动时重定向程序运行路径及 Windows 服务运行模式部署

日常工作中有时候会遇到需要将程序直接在服务器上运行,而不依赖于 IIS 托管的情况,直接运行有两种方式,一种是部署为 服务模式,另一种则是 直接启动 .NET 发布之后的 exe 文件以 控制台模式运行,控制台模式运行主要问题…

Unexpected end of JSON input while parsing near错误解决方式(网上的方法)

原本是想创建一个create-react-app来着,但是在创建的中间会出现Unexpected end of JSON input while parsing near... 的错误。 在网上找到了一些方法,首先是清空npm的缓存。 npm cache clean --force 氮素,然并卵。near后面的内容变化了一下…

Xmemcached学习笔记一(安装memcached)

memcached有三种java客户端 第一种:Com.danga 包下面的memcached,需引入jar(本人用的是memcached-2.5.2.jar 文末附上附件需要的可以下载) 第二种:spyMemcached 第三种:XMemcached 据说第三种是使用最简单,最好用的&a…

WrapPanel 实现虚拟化

WrapPanel 实现虚拟化控件名:VirtualizingWrapPanel作者:WPFDevelopersOrg原文链接: https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用大于等于.NET40;Visual Studio 2022;项目使用 MIT 开源许可协议;众…

如何证明 ConcurrentDictionary 字典操作不全是线程安全的

前言最近,看到一篇文章,讲到《ConcurrentDictionary字典操作竟然不全是线程安全的?》。首先,这个结论是正确的,但文中给出的一个证明例子,我觉得是有问题的。相关代码如下:using System.Collect…

16-djongo中间件学习

目录 前戏 我们在前面的课程中已经学会了给视图函数加装饰器来判断是用户是否登录,把没有登录的用户请求跳转到登录页面。我们通过给几个特定视图函数加装饰器实现了这个需求。但是以后添加的视图函数可能也需要加上装饰器,这样是不是稍微有点繁琐。 学完…