《重学Java高并发》Sempahore的使用场景与常见误区
大家好,我是威哥,《RocketMQ技术内幕》一书作者,荣获RocketMQ官方社区优秀布道师、CSDN2020博客执之星Top2等荣誉称号。目前担任中通快递技术平台部资深架构师,主要负责全链路压测、消息中间件、数据同步等产品的研发与落地,拥有千亿级消息集群的运维经验,不仅实践经验丰富,而且对其源代码有深入且系统的研究。欢迎大家关注我,一起抱团发展。
JUC,java并发框架也是面试中的常客,而Semaphore信号量又是JUC的重头戏。
Semaphore简单吗?使用起来非常简单,但最近生产环境遇到一个故障,最终原因竟然是Semaphore在多线程异步环境使用不当造成的?
读者朋友们一定会觉得很诧异,容我慢慢道来。
文章开头时先吹一个牛:我有一双“火眼金睛”,任你刷多少题,是否用过Semaphore,我一问便知。
1、Sempahore使用场景
读者朋友们对下面的对话我想肯定不会陌生:
面试官:看你简历中写到你熟悉多线程编程,那你的多线程工具包有哪些工具?
候选人:多线程jdk提供了丰富的工具,都集中在JUC包中,通常有线程池、Semaphore、CountDownLatch、原子类等。
面试官:那你能说说信号量Semaphore通常在什么场景下使用呢?
候选人:限流
面试官:如何基于Semaphore进行限流?
候选人:。。。。。。
面试官:看图说话,这段代码是否正确?

Sempahore信号量最经典的使用场景是限流,用于控制并发度。
回想我们在开发系统时免不了要对接第三方系统,例如在快递行业的用户端系统(寄件)时,用户通过微信小程序进行下单时,需要手动填写收件人、寄件人信息等信息,十分繁琐与低效,能否从产品角度加以改善?
当然可以,产品提成上传一张包含收件地址等信息等图片,通过AI等技术识别图片,自动提取图片中的有效信息并自动填充,提高用户体验。
通过技术选型,我们敲定了百度的免费(付费)图片识别接口,但第三方提供的配额是有限的,特别是会限制接口的并发度,超过并发度的接口将会返回错误,特别是免费类的接口更加如此?
该如何处理呢?Sempahore闪亮登场。
2、许可超额现象及解决方案
Sempahore的使用场景非常经典,其使用看上区非常简单,其基本的使用代码如下:

上面的方法就是控制doSomeThing()方法的调用并发度,即同一时间允许多少个线程并发执行doSomeThing()方法中的代码,简单说明一下:
- 在创建Sempahore对象时指定该信号量中包含的许可数量。
- 在执行业务方法之前,首先向信号量对象中申请一个许可,如果申请到,acquire()方法立即返回,执行完成业务逻辑放一定要调用release()方法,释放许可。
- 如果当前许可已全部申请,其他线程调用信号量的acquire()方法时会阻塞,当然acquire方法也可以设置超时时间,该方法的返回结果表示是否申请到许可。
温馨提示:上面的代码有漏洞,你能发现吗?
上面的代码有可能造成多释放,当10个线程分别去获取许可,都能成功,但都在执行doSomeThing()方法的时候,第11个线程尝试获取许可,但可能发生中断等异常导致没成功获取许可而触发异常,但最终进入到finally语句块,进行释放许可,这样就会增加许可数量,导致逻辑异常,这样的问题特别是在调用带超时时间的acquire方法时更加明显,其正确的使用如下图所示:

3、许可不释放导致无限阻塞
上面只是“小试牛刀”,使用Sempahore真正的挑战在于多线程环境中,我们回到开头部分的场景,调用第三方接口解析图片,并使用多线程提高并发度,示例代码如下:

该示例主要是结合CompletableFuture实现多线程并发,并结合信号量控制调用第三方接口的并发度(这里用doSomeThing()方法表示)。
温馨提示:CompleteableFuture的whenComplete事件回调函数是发生异常时也会进入,并且第二个参数为异常对象。
在JDK8中CompletableFuture暂不支持设置超时时间,故本例使用了CountDownLatch用来控制test02方法的超时时间。
上面的示例是不是非常给力,但如果细看,可能存在许可不及时释放的问题,也就示说semaphore的release()方法不会执行,因为上述方法会超时。
许可不释放带来的后果非常严重,因为后续申请的时候由于一直没有许可,将无法获取许可,而无法执行业务逻辑。
初步解决思路就是在cdh.awiat方法结束后判断是否超时,如果超时,手动释放许可,例如下图所示:

这样的想法好是好,但这样会造成许可的多次释放,最终导致许可数量增加,超过预期。
这其实是要求seaphore的release方法会在不同条件下在不同地方会被调用,但同一个请求只在其中一个地方被执行。
要解决这个问题,我想大家会自然而然的想到JUC中的另外的工具:原子类,尽管多次调用,我们只需第一次调用时真正释放许可,其他调用则直接忽略即可。
解决方案如下:

引入一个包装类,包装Sempahore,并结合AtomicBoolean,保证每一个SempahoreReleaseOnlyOne对象只会释放Sempahore一次。
引入该类后,上面的代码改造如下:

本文就介绍到这里了,Semaphore看似简单,但要用好用对还是有难度的,不知各位是否Get到了。如果需要整套源码,可以发送私信:SCODE,即可获取。
一键三连(关注、点赞、留言)是对我最大的鼓励。
掌握一到两门java主流中间件,是敲开BAT等大厂必备的技能,送给大家一个Java中间件学习路线,助力大家早日进入互联网大厂。
最后分享笔者一个硬核的RocketMQ电子书,您将获得千亿级消息流转的运维经验。

获取方式:RocketMQ电子书。
文章首发:https://www.codingw.net/posts/fa8c5e0b.html
《重学Java高并发》Sempahore的使用场景与常见误区的更多相关文章
- 跟着阿里p7一起学java高并发 - 第18天:玩转java线程池,这一篇就够了
java中的线程池,这一篇就够了 java高并发系列第18篇文章. 本文主要内容 什么是线程池 线程池实现原理 线程池中常见的各种队列 自定义线程创建的工厂 常见的饱和策略 自定义饱和策略 线程池中两 ...
- 跟着阿里p7一起学java高并发 - 第19天:JUC中的Executor框架详解1,全面掌握java并发核心技术
这是java高并发系列第19篇文章. 本文主要内容 介绍Executor框架相关内容 介绍Executor 介绍ExecutorService 介绍线程池ThreadPoolExecutor及案例 介 ...
- java高并发系列 - 第25天:掌握JUC中的阻塞队列
这是java高并发系列第25篇文章. 环境:jdk1.8. 本文内容 掌握Queue.BlockingQueue接口中常用的方法 介绍6中阻塞队列,及相关场景示例 重点掌握4种常用的阻塞队列 Queu ...
- Java高并发系列——检视阅读
Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...
- java高并发系列 - 第27天:实战篇,接口性能成倍提升,让同事刮目相看,现学现用
这是java高并发系列第27篇文章. 开发环境:jdk1.8. 案例讲解 电商app都有用过吧,商品详情页,需要给他们提供一个接口获取商品相关信息: 商品基本信息(名称.价格.库存.会员价格等) 商品 ...
- java高并发系列 - 第12天JUC:ReentrantLock重入锁
java高并发系列 - 第12天JUC:ReentrantLock重入锁 本篇文章开始将juc中常用的一些类,估计会有十来篇. synchronized的局限性 synchronized是java内置 ...
- Java高并发秒杀系统API之SSM框架集成swagger与AdminLTE
初衷与整理描述 Java高并发秒杀系统API是来源于网上教程的一个Java项目,也是我接触Java的第一个项目.本来是一枚c#码农,公司计划部分业务转java,于是我利用业务时间自学Java才有了本文 ...
- java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能
这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能 ...
- java高并发系列 - 第21天:java中的CAS操作,java并发的基石
这是java高并发系列第21篇文章. 本文主要内容 从网站计数器实现中一步步引出CAS操作 介绍java中的CAS及CAS可能存在的问题 悲观锁和乐观锁的一些介绍及数据库乐观锁的一个常见示例 使用ja ...
随机推荐
- appium+Andriod环境搭建遇到问题
报错:Caused by: org.openqa.selenium.WebDriverException: An unknown server-side error occurred while pr ...
- Mybatis 二级缓存应用 (21)
[MyBatis 二级缓存] 概述:一级缓存作用域为同一个SqlSession对象,而二级缓存用来解决一级缓存不能夸会话共享,作用范围是namespace级,可以被多个SqlSession共享(只要是 ...
- ORB_SLAM3 -- 配置安装
安装环境 Ubuntu20.04 ORB_SLAM3依赖项安装 opencv3 ORB_SLAM3可用opencv3或opencv4编译,作者这里安装测试了opencv3 Step1: 安装openc ...
- 反调试——11——检测TF标志寄存器
反调试--11--检测TF标志寄存器 在intel的x86寄存器中有一种叫标志寄存器: 标志寄存器中的TF(Trap Flag)位,CPU在执行完一条指令后,如果检测到标志寄存器的TF位为1,则会产生 ...
- 敏捷 Scrum Master 的難點
什麼是 Scrum Master? Scrum master 是一個團隊角色,負責確保團隊遵守敏捷方法和原則並符合團隊的流程和實踐. Scrum Master 促進敏捷開發團隊成員之間的協作.Scru ...
- 安装多个版本的 JDK
安装多个版本的 JDK 刚刚开始学 Java 的时候安装了 JDK9 版本,后续发现还是 JDK8 使用的多些,而又不想删除原先版本 因此安装两个版本的 JDK 在需要是切换一下即可 1. 安装第一个 ...
- elasticsearch的索引重建
我们知道es在字段的mapping建立后就不可再次修改mapping的值.在我们实际的情况下有些时候就是需要修改mapping的值,解决方案就是重新构建索引数据. 方式一 : 使用索引别名,创建另外一 ...
- Java版流媒体编解码和图像处理(JavaCPP+FFmpeg)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Noip模拟79 2021.10.17(题目名字一样)
T1 F 缩点缩成个$DAG$,然后根据每个点的度数计算期望值 1 #include<cstdio> 2 #include<cstring> 3 #include<vec ...
- 2021.9.13考试总结[NOIP模拟52]
T1 路径 考虑每一位的贡献,第$i$位每$2^i$个数会变一次,那么答案为$\sum_{i=1}^{log_2n} \frac{n}{2^i}$. $code:$ 1 #include<bit ...