java并发高频面试题:Sempahore的使用场景与常见误区

简介: java并发高频面试题:Sempahore的使用场景与常见误区

1、Sempahore使用场景


读者朋友们对下面的对话我想肯定不会陌生:


面试官:看你简历中写到你熟悉多线程编程,那你的多线程工具包有哪些工具?


候选人:多线程jdk提供了丰富的工具,都集中在JUC包中,通常有线程池、Semaphore、CountDownLatch、原子类等。


面试官:那你能说说信号量Semaphore通常在什么场景下使用呢?


候选人:限流


面试官:如何基于Semaphore进行限流?


候选人:。。。。。。


面试官:看图说话,这段代码是否正确?

63d72c5dbad9a5ed8e39266507e5c24f.jpg

Sempahore信号量最经典的使用场景是限流,用于控制并发度。


回想我们在开发系统时免不了要对接第三方系统,例如在快递行业的用户端系统(寄件)时,用户通过微信小程序进行下单时,需要手动填写收件人、寄件人信息等信息,十分繁琐与低效,能否从产品角度加以改善?


当然可以,产品提成上传一张包含收件地址等信息等图片,通过AI等技术识别图片,自动提取图片中的有效信息并自动填充,提高用户体验。


通过技术选型,我们敲定了百度的免费(付费)图片识别接口,但第三方提供的配额是有限的,特别是会限制接口的并发度,超过并发度的接口将会返回错误,特别是免费类的接口更加如此?


该如何处理呢?Sempahore闪亮登场,可通过发放一定数量的许可从而控制并发度


2、许可超额现象及解决方案


Sempahore的使用场景非常经典,其使用看上去非常简单,其基本的使用代码如下:

63d72c5dbad9a5ed8e39266507e5c24f.jpg

上面的方法就是控制doSomeThing()方法的调用并发度,即同一时间允许多少个线程并发执行doSomeThing()方法中的代码,简单说明一下:


  • 在创建Sempahore对象时指定该信号量中包含的许可数量。
  • 在执行业务方法之前,首先向信号量对象中申请一个许可,如果申请到,acquire()方法立即返回,执行完成业务逻辑放一定要调用release()方法,释放许可。
  • 如果当前许可已全部申请,其他线程调用信号量的acquire()方法时会阻塞,当然acquire方法也可以设置超时时间,该方法的返回结果表示是否申请到许可。

温馨提示:上面的代码有漏洞,你能发现吗?


上面的代码有可能造成多释放,当10个线程分别去获取许可,都能成功,但都在执行doSomeThing()方法的时候,第11个线程尝试获取许可,但可能发生中断等异常导致没成功获取许可而触发异常,但最终进入到finally语句块,进行释放许可,这样就会增加许可数量,导致逻辑异常,这样的问题特别是在调用带超时时间的acquire方法时更加明显,其正确的使用如下图所示:

f69a5a2204d84b0161126d202ae74f00.png


3、许可不释放导致无限阻塞


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

2da503305a3ebd5573d40e93f0c11acb.png

该示例主要是结合CompletableFuture实现多线程并发,并结合信号量控制调用第三方接口的并发度(这里用doSomeThing()方法表示)。


温馨提示:CompleteableFuture的whenComplete事件回调函数是发生异常时也会进入,并且第二个参数为异常对象。


在JDK8中CompletableFuture暂不支持设置超时时间,故本例使用了CountDownLatch用来控制test02方法的超时时间。


上面的示例是不是非常给力,但如果细看,可能存在许可不及时释放的问题,也就示说semaphore的release()方法不会执行,因为上述方法会超时。


许可不释放带来的后果非常严重,因为后续申请的时候由于一直没有许可,将无法获取许可,而无法执行业务逻辑。


初步解决思路就是在cdh.awiat方法结束后判断是否超时,如果超时,手动释放许可,例如下图所示:

29f90116f6af8823044f043e22e9205a.png

这样的想法好是好,但这样会造成许可的多次释放,最终导致许可数量增加,超过预期。


这其实是要求seaphore的release方法会在不同条件下在不同地方会被调用,但同一个请求只在其中一个地方被执行。


要解决这个问题,我想大家会自然而然的想到JUC中的另外的工具:原子类,尽管多次调用,我们只需第一次调用时真正释放许可,其他调用则直接忽略即可。


解决方案如下:

ba535e615b5fe78dcf1a7a58cd4316a2.png

引入一个包装类,包装Sempahore,并结合AtomicBoolean,保证每一个SempahoreReleaseOnlyOne对象只会释放Sempahore一次。


引入该类后,上面的代码改造如下:

46aa6b4c5d96282558df5fb8deb2f749.png

本文就介绍到这里了,Semaphore看似简单,但要用好用正确还是有难度的,不知各位是否Get到了。

相关文章
|
5天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
10天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
15天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
11天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
37 4
|
12天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
53 4
|
13天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
18天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
39 2
|
24天前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
52 5
|
23天前
|
存储 Java
[Java]面试官:你对异常处理了解多少,例如,finally中可以有return吗?
本文介绍了Java中`try...catch...finally`语句的使用细节及返回值问题,并探讨了JDK1.7引入的`try...with...resources`新特性,强调了异常处理机制及资源自动关闭的优势。
19 1
|
22天前
|
算法 Java
JAVA 二叉树面试题
JAVA 二叉树面试题
14 0