前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >并发编程之显式条件

并发编程之显式条件

作者头像
Single
发布于 2019-05-25 09:09:55
发布于 2019-05-25 09:09:55
36400
代码可运行
举报
运行总次数:0
代码可运行

我们之前介绍 synchronized 关键字语义的时候说过,synchronized 虽然不需要我们手动的加锁和释放锁了,但不代表他没有用到锁。同时,我们说每个对象本身结构中也内置了阻塞队列,线程持有器,锁重入计数器等字段。

所以,与其说是 synchronized 实现了「自动化的锁机制」,不如说是 synchronized 借助了我们 Java 中的对象结构实现了了「自动化的锁机制」。

虽然,我们通过 synchronized 对线程实现了自动化的阻塞与唤醒,但是对于已经获得锁的线程来说,如果在他们的执行期间缺少了某些条件以继续执行,比如调用了数据库服务等待数据回显,那么我们从 CPU 的使用效率来看是不应该让当前线程继续持有 CPU 空等待的。

wait 方法使用在 synchronized 内部,专门用于将那些已经获得锁但由于缺乏某些条件不能继续执行的线程阻塞到另一个队列上,并释放锁及 CPU。同理,notify 方法就是从等待的队列上释放一个线程以标识它的条件可能满足了,让它尝试重新竞争锁。

而在我们的显式锁中,对应 wait/notify 语义的就是我们本篇要讨论的『显式条件』,我们一起来看看。

实现原理

在探究『显式条件』的实现原理之前,我们先通过一个小的代码 demo,看看显式条件是如何使用的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

Thread thread1 = new Thread(){
    @Override
    public void run(){
        lock.lock();
        System.out.println("preparing waiting.....");
        try {
            condition.await();
        } catch (InterruptedException e) {}
        System.out.println("waiting end.......");
                
        lock.unlock();
    }
};
thread1.start();

为了缩短篇幅,没给全方法体,只抽出来核心的部分代码,具体完整的代码,大家可以去 github 自行查看下载。

得到一个显式条件,还是很简单的,我们只要通过 ReentrantLock 的 newCondition 方法即可获得一个条件对象。接着,在获取到锁之后如果遇到某些条件不满足,不能继续执行了,直接调用 Condition 实例的 await 方法即可,释放一个条件队列上的线程调用 signal 即可,不再赘述。

这里需要注意一点的是,正如同 synchronized 中使用 wait/notify 方法一样,condition 的两个方法 await 和 signal 也一样是需要在 lock 方法之后调用的,也即是必须先获得锁才有机会被条件等待

下面我们看实现原理,先从 newCondition 方法开始:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Condition newCondition() {
    return sync.newCondition();
}

直接透传调用的 AQS 中的 newCondition 方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final ConditionObject newCondition() {
    return new ConditionObject();
}

ConditionObject 是 AQS 中定义的一个内部类,并实现了 Condition 接口,是一个真正的显式条件实现者。我们直接看看它的 await 方法:

我用不同颜色的实线将 await 方法切分成了四个模块,每一个模块我们进行一个整体上的概括,具体的代码实现大家自行研究,也欢迎加我微信讨论。

分析之前首先需要明确等待队列和阻塞队列虽然共用的同一个节点类 Node,但是确实两个完全不同的队列。阻塞队列使用 next 指针链接下一个节点,等待队列使用的是 nextWaiter 指针链接下一个节点。

这一点需要有一个前提认识,不然等你分析具体代码实现的时候,你会不知所措的。 下面我们总结这个四个部分的大致逻辑:

  1. 将当前线程包装成节点追加到等待队列的尾部,因为这个时候还没有释放锁,所以追加过程是无并发风险的,接着释放自己持有的显式锁。
  2. 如果条件满足了或是被其他线程移除出等待队列了,那么 isOnSyncQueue 就会返回 true,结束循环并尝试第三步,否则将会被阻塞当前线程并等待唤醒。
  3. 从等待队列中移除之后依然需要先尝试获取显式锁,接着才能返回到当初被阻塞的调用处。
  4. 处理中断,抛出异常或是设置中断标志位。

这就是 await 方法的实现逻辑,再简洁一点的概括就是:

释放持有的锁-->阻塞自己等待被唤醒-->被唤醒后先尝试获取锁-->处理下中断-->方法返回

总的来说,并不难理解,接着我们看 signal 方法的实现逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

如果自己并没有持有锁而试图去释放等待队列上的线程节点,直接抛出异常,拒绝访问。反之,拿到等待队列头节点,并调用 doSignal 释放一个阻塞的节点线程。

doSignal 方法的实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
        (first = firstWaiter) != null);
}

方法逻辑主要分为两个部分,一个是循环体,一个是循环条件。循环体做的事情就是置换出来第等待队列上的第一个节点,让它与队列脱钩。循环条件里面的逻辑就是,尝试将刚才脱钩的节点转移到阻塞队列上。

如果转移失败了,只有一种可能,就是当前需要被转移的节点的等待状态不再是 CONDITION,也即是他的等待状态已经被取消,所以我们就不需要关心它了,顺着等待队列找到下一个有效的节点,尝试去转移并唤醒其对应的线程。

整个循环只有成功释放了一个节点线程,才能结束。

这样,我们对于 condition 中的两个核心的方法原理就分析完了,相信你一定有所了解了,至于其他的一些 signalAll,awaitNanos 以及 await 的几个重载方法来说,他们大多离不开以上分析的两个方法,这里就不再赘述了。

生产者消费者模型实现

下面我们应用一下上面介绍的『显式条件』,通过实现一个经典的并发模型场景,之前我们是通过 wait/notify 实现的,生产者和消费者公用了同一个条件等待队列,相对来说是不太合适的,效率是不如我们的显式条件的。

为什么这么说呢?

因为我们的显式条件依附于显式锁,是可以创建多个的,所以对于生产者与消费者来说,我们可以创建两个不同的条件等待队列分别来阻塞条件不满足的线程,唤醒的时候也可以「对症下药」,不需要同时唤醒所有的生产者与消费者。这一点,我希望你能有所体会。

我们定义一个仓库类,提供添加和消费产品的能力,当然这两种方式是并发安全的。对于生产方法来说,当仓库满了则不能继续生产产品,而需要在等待队列上进行等待。

对于消费方法来说,当仓库为空是则不能继续消费产品,而需要在另一个等待队列上进行等待。

当然,如果成功生产了一个产品,将尝试唤醒所有消费者,告诉他们仓库有产品了,反之,如果成功消费了一个产品,将尝试唤醒所有的生产者,告诉他们仓库中有空余位置了,你们可以继续生产了。

这样,当我们同时创建大量的生产者与消费者,并让他们并发调用这两个方法,消费者与生产者的足迹会交替的出现在控制台上。这部分代码我已经写好了,并且测试过了,因为限于篇幅且不是核心代码,这里就不贴出来了,大家可以自行尝试编写或从我的 github 上下载我已经完成的实现,欢迎你给我提出建议!

关于锁的相关内容,我们大致介绍完了,下一篇将介绍 Java 中的异步任务与线程池的概念。

每篇文章用到的所有案例代码素材都会上传我个人 github

https://github.com/SingleYam/overview_java

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-01-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
客户端方便快捷
客户端方便快捷
回复回复点赞举报
推荐阅读
JFrog Artifactory
Artifactory 是一个存放制品(Artifacts)的工具。当前,Artifactory 是一个非常有影响力,功能非常强大的工具。
ZHANGHAO
2022/06/19
2.3K0
制品库实践: Jenkins&Nexus&Artifactory集成
本章我们主要讲述Jenkins与制品库nexus、artifactory集成,上传下载制品。
DevOps云学堂
2020/02/26
5.2K0
一分钟开始持续集成之旅系列之:Java + GWT
Google Web Toolkit(GWT)是一个开源、免费的 Web 开发框架,通过该框架,您可以使用 Java 构建复杂、高性能的 JavaScript 应用程序。
腾讯云 CODING
2020/06/23
1.4K0
一分钟开始持续集成之旅系列之:Java + GWT
写给那些想使用 JFrog Artifactory 管理制品的人
我在使用 Artifactory 做持续集成已经有一段时间了,对企业级 Artifactory 也有了一些经验和总结,希望能通过本篇的分享帮助刚接触这个工具的人了解什么是Artifactory,它能做什么,为什么要选择它,以及在使用过程中应该注意什么。
Peter Shen
2020/11/12
5.5K1
持续集成流水线中的制品管理(Nexus)
我们可以在该工作流中通过Maven和CI服务器来构建,存储,管理已编译完成的制品。
DevOps云学堂
2021/10/18
1.7K0
持续集成流水线中的制品管理(Nexus)
conan入门(一):conan 及 JFrog Artifactory 安装
Conan是通用且便携的。它适用于所有操作系统,包括 Windows、Linux、OSX、FreeBSD、Solaris 等,并且可以针对任何平台,包括桌面、服务器以及嵌入式和裸机设备的交叉构建。它与 Docker、MinGW、WSL 等其他工具以及 CMake、MSBuild、Makefiles、Meson、SCons 等所有构建系统集成。它甚至可以与任何专有的构建系统集成。
10km
2022/04/13
5K0
conan入门(一):conan 及 JFrog Artifactory 安装
Maven 私服你应该不陌生吧,可你会用 Artifactory 搭建吗?
JFrog Artifactory 是一个 Artifacts 仓库管理平台,它支持所有的主流打包格式、构建工具和持续集成(CI)服务器。它将所有二进制内容保存在一个单一位置并提供一个接口,这使得用户在整个应用程序开发和交付过程中,能更易于上传、查找和使用二进制文件。
iMike
2020/05/05
2.1K0
集成构建工具
嘿嘿,大家好。 构建工具是用来将代码编译打包成制品的工具。例如前端项目我们一般使用npm进行打包,后端java项目我们一般使用maven、gradle进行打包。构建工具很多很多,但是集成到gitlab中是一样的。所以这里简单介绍使用gitlabCI集成npm/maven完成前后端项目的构建。
章工运维
2024/04/16
1260
集成构建工具
工欲善其事,必先利其器——DevOps中如何管理工具包
作为DevOps交付流水线的开发者,为支持CI/CD中各项任务的自动化,都需要依赖多种包管理工具来下载各种相关的工具,比如针对产生最终交付件的构建过程,就需要在构建流程的第一步,自动地把相关工具,如Curl、wget、Maven、Gradle、npm等等,下载到CI服务器。这些工具的下载,通常都需要依靠对应的公网服务器和包管理工具来支持。而这样通过公网来下载工具,有时会遇到稳定性的问题,也就是所谓的环境问题,导致工具下载失败,进而导致构建任务的失败。因此,我们需要引入新的技术来克服这些问题,保证工具包下载的稳定和可靠。
JFrog杰蛙科技
2020/04/01
1.2K0
GoCenter助力Golang全速前进
Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。自2009年11月Google正式宣布推出,成为开放源代码项目以来,Go语言已成为当今开发人员和DevOps领域最流行的语言之一, 它被用于设计和编写Kubernetes和Helm。但是,相比语言本身已经得到了广泛的普及和使用,Go语言的包管理方案却大大滞后了。
JFrog杰蛙科技
2020/01/08
1.2K0
GoCenter助力Golang全速前进
Artifactory & GitLab CI持续集成实践
GitLab CI支持创建多个构建,并评估每次代码提交是否通过测试和以及对您产品的影响。在构建过程中,会生成大量二进制文件,如果不能正确的大规模管理这些文件,就会导致二进制文件管理混乱。为了克服这个问题,Artifactory被无缝地集成到GitLab CI构建过程中,以便更好的发布和管理这些二进制文件,并通过JFrog CLI, GitLab CI缓存、发布您的依赖包、制品包和构建信息到Artifactory。
JFrog杰蛙科技
2020/02/19
2.1K0
Artifactory & GitLab CI持续集成实践
聊聊DevOps制品管理-不止是存储这么简单
「制品」是指由源码编译打包生成的二进制文件,不同的开发语言对应着不同格式的二进制文件;这些二进制文件通常用于运行在服务器上或者作为编译依赖,“制品的管理”是配置管理的重要组成部分。
DevOps在路上
2023/06/23
1K0
聊聊DevOps制品管理-不止是存储这么简单
软件持续交付速度提升 40%!DevOps 制品管理有何魔力?
作者 | 张雅文 近年来,混合云、多云正逐步成为企业用云的主流模式。据 IBM 的调查报告显示,仅截至 2021 年,采用混合云、多云战略的企业就已经接近 80%。混合云、多云战略的确能够增加企业资源配置的灵活性,但也给持续交付带来了更大的挑战。在软件发布频率持续增长趋势下,如何将版本快速分发到多个环境中去,成为令不少开发者头疼的问题。 近日,亚马逊云科技联合 JFrog 举行 《DevOps 实践:混合云模式下软件单一可信源的建设方法》为主题的 Tech Talk,JFrog (中国)技术总监王青与大家
深度学习与Python
2023/03/29
1.2K0
软件持续交付速度提升 40%!DevOps 制品管理有何魔力?
使用GitLabCI模板库的流水线优化实践
作业分为Build、test、codeanalysis、artifactory、deploy部分,在每个作业中配置了rules功能开关,由变量控制最终作业的运行。
DevOps云学堂
2020/06/02
2.1K0
使用GitLabCI模板库的流水线优化实践
CI&CD夺命十三剑9-Sonar Scanner使用配置&SonarQube项目命令行接入
在前面一篇《代码质量扫描工具SonarQube原理及环境搭建》中,我们介绍了Sonarqube的架构组成、工作原理以及环境搭建相关操作。本篇将会重点介绍:
大刚测试开发实战
2023/08/29
2.7K0
CI&CD夺命十三剑9-Sonar Scanner使用配置&SonarQube项目命令行接入
使用Artifactory集群作为文件共享中心
NFS和云储存的方式对网络要求很高,稳定性得不到保证。自研的方式需要投入很多人力物力,利用SCM版本控制工具对二进制文件支持不好,尤其是大文件,还有可能会对构建效率造成影响。可以看到上面几种方式稳定性不能保证,而且需要额外的投入。
JFrog杰蛙科技
2020/03/24
1.8K0
使用Artifactory集群作为文件共享中心
Docker搭建sonarqube
SonarQube 是一个用于代码质量管理的开源平台,用于管理源代码的质量。同时 SonarQube 还对大量的持续集成工具提供了接口支持,可以很方便地在持续集成中使用 SonarQube。此外 SonarQube 的插件还可以对 Java 以外的其他编程语言提供支持,对国际化以及报告文档化也有良好的支持。
对你无可奈何
2021/05/08
6.5K0
Artifactory清理未使用的二进制品的最佳实践
Artifactory充分利用了基于Checksum的存储,但是这种机制无法代替常规的工件清理任务。软件开发可能很杂乱,很多时候Artifactory中的许多工件都从未使用过。
JFrog杰蛙科技
2020/03/10
3.7K0
Artifactory清理未使用的二进制品的最佳实践
为您的DevSecOps锦上添花——JFrog Xray的新功能
当前,随着比较常用的组件,如Tomcat、Docker、Kubernetes等陆续曝出存在高危漏洞,组件安全已成为业界日益关注的安全扫描新的重要分支。必须在DevOps流程中加强针对组件的安全扫描,这也是当前业界推荐的DevSecOps的重要组成部分。
JFrog杰蛙科技
2021/06/09
1.8K0
为您的DevSecOps锦上添花——JFrog Xray的新功能
『如何构建命令行工具:YiYi』
封面.png 大家好,我是谢伟,是一名程序员。 过去一阵子,我在开发一款客户端命令行工具,业余时间,开始写了下面这个工具。仅做学习参考使用。现在它看上去不够优雅,命令的命名也没有好好推敲。但功能都已实现。 即如何构建一个命令行工具,希望通过这个项目的示例,你能开发出各种各样符合你需求的命令行工具。 比如 github 上非常热门的命令行项目: annie 下载视频和图片工具 hub 一个包装git 操作github 的工具 jfrog-cli-go 一个仓库管理平台的客户端 ... 开始之前,还是看几
谢伟
2018/06/06
7490
推荐阅读
相关推荐
JFrog Artifactory
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档