条件锁

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…

每天一个linux命令(6):rmdir 命令

今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的。(注意,rm - r dir命令可代替rmdir,但是有很大危险性。)删除某目录时也必须具…

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

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

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

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

APP地推心得:可复制的APP地推方案

APP地推难?APP地推方案包含哪些?现在,不需要编程就能自己完成手机APP制作,而且还有大量的APP模板,可以直接套用。APP的制作资金技术大幅度降低,现在最大的问题就是怎么APP推广的问题。 在移动互联网的时代&…

【代码笔记】iOS-播放从网络上下载的语音

代码&#xff1a; ViewController.m #import "ViewController.h" //录音 #import <AVFoundation/AVFoundation.h>interface ViewController () {//播放器AVAudioPlayer *player; }endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];/…

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

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

PHP资源列表(转)

一个PHP资源列表&#xff0c;内容包括&#xff1a;库、框架、模板、安全、代码分析、日志、第三方库、配置工具、Web 工具、书籍、电子书、经典博文等等。 初始翻译信息来自&#xff1a;《推荐&#xff01;国外程序员整理的 PHP 资源大全》 该内容也可以在github的相关项目上浏…

当今 计算机已进入千家万户英语,学生英语教学论文,关于信息技术在大学英语教学中的应用探析相关参考文献资料-免费论文范文...

导读:这是一篇与学生英语教学论文范文相关的免费优秀学术论文范文资料,为你的论文写作提供参考。摘 要&#xff1a;随着经济全球化和信息化时代的到来,计算机技术已深入到社会的各个领域.以往大学中所使用的传统面授课教学模式已经跟不上信息时代的发展步伐,不能满足当今教学的…

POJ - 2187 Beauty Contest(最远点对)

http://poj.org/problem?id2187 题意 给n个坐标&#xff0c;求最远点对的距离平方值。 分析 模板题&#xff0c;旋转卡壳求求两点间距离平方的最大值。 #include<iostream> #include<cmath> #include<cstring> #include<queue> #include<vector>…

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

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

Mysql-索引的基础和类型

一、 索引的基础 索引类似于书籍的目录&#xff0c;要想找到一本书的某个特定主题&#xff0c;需要先查找书的目录&#xff0c;定位对应的页码。 存储引擎使用类似的方式进行数据查询&#xff0c;先去索引当中找到对应的值&#xff0c;然后根据匹配的索引找到对应的数据行 二…

ligerUI的列头合并代码片段

//列头合并 function onAfterShowData(data){//显示数据前触发此事件 console.log(123); var k 0; var tr $(.l-grid-body.l-grid-body1).find(table tr);//找到被冻结的列&#xff08;frozen&#xff09;,利用find方法找到所有的行 $.each($(tr)…

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

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

RDS for MySQL Mysqldump常见问题及处理

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

AI求解PDE

一、波动方程的PINN解法: Guo Y, Cao X, Liu B, et al. Solving partial differential equations using deep learning and physical constraints[J]. Applied Sciences, 2020, 10(17): 5917. 二、二维的Navier–Stokes方程组的PINN解法 矢量形式的不可压缩Navier-Stokes方程…

使用CADisplayLink实现UILabel动画特效

在开发时&#xff0c;我们有时候会遇到需要定时对UIView进行重绘的需求&#xff0c;进而让view产生不同的动画效果。 本文项目 效果图 初探 CADisplayLink 定时对View进行定时重绘可能会第一时间想到使用NSTimer&#xff0c;但是这样的动画实现起来是不流畅的&#xff0c;因为在…

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

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

Mysql的关联查询语句

一 内连接( inner join&#xff09; 1、多表中同时符合某种条件的数据记录的集合 (取两表公共部分) 2、inner join 可以缩写成 join 例如: select * from A,B WHERE A.idB.id 或者 select * from A inner join B on A.idB.id 内连接分为三类:{ &#xff08;1&#xff0…