今天我们来聊一聊Java中的Semaphore

2024-04-13 21:20

本文主要是介绍今天我们来聊一聊Java中的Semaphore,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

写在开头

在上几天写《基于AQS手写一个同步器》时,很多同学留言说里面提到的Semaphore,讲得太笼统了,今天趁着周末有空,咱们就一起详细的学习和梳理一把 Semaphore

什么是Semaphore?

在前面我们讲过的synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,而Semaphore(信号量)可以用来控制同时访问特定资源的线程数量,多线程同时操作共享资源,仍然存在着线程不安全问题,要想多线程安全,理应结合锁进一步保障。

Semaphore的主要结构

我们跟进信号量的源码中浏览一圈,发现其实它内部主要的方法就2个:

// 初始共享资源数量
final Semaphore semaphore = new Semaphore(5);
// 获取1个许可
semaphore.acquire();
// 释放1个许可
semaphore.release();

① acquire():获取许可

跟进这个方法后,我们会发现其内部调用了AQS的一个final 方法acquireSharedInterruptibly(),这个方法中又调用了tryAcquireShared(arg)方法,作为AQS中的钩子方法,这个方法的实现在Semaphore的两个静态内部类 FairSync(公平模式)NonfairSync(非公平模式) 中。

【源码解析1】

/***  获取1个许可证*/
public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
/*** 共享模式下获取许可证,获取成功则返回,失败则加入阻塞队列,挂起线程*/
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 尝试获取许可证,arg为获取许可证个数,当可用许可证数减当前获取的许可证数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}

继续跟入tryAcquireShared(arg)方法,虽然它在AQS中,但它作为钩子方法,最终的实现则回到了Semaphore的内部类中。

钩子方法: 一种抽象类中的方法,一般使用 protected 关键字修饰,可以给与默认实现,空方法居多,其内容逻辑由子类实现,为什么不适用抽象方法呢?因为,抽象方法需要子类全部实现,增加大量代码冗余!

在这里插入图片描述

【扩展】

  1. FairSync(公平模式): 调用 acquire() 方法的顺序就是获取许可证的顺序,遵循 FIFO;
  2. NonfairSync(非公平模式): 抢占式的,在构造方法中通过一个布尔值fair来配置公平与非公平,默认为非公平模式。

② release():释放许可

同样跟入这个方法,里面用了AQS的releaseShared(),而在这个方法内也毫无疑问的用了tryReleaseShared(int arg)这个钩子方法,原理同上,不再冗释,需要注意的是释放共享锁的同时也会唤醒同步队列中的一个线程。

【源码解析2】

// 释放一个许可证
public void release() {sync.releaseShared(1);
}// 释放共享锁,同时会唤醒同步队列中的一个线程。
public final boolean releaseShared(int arg) {//释放共享锁if (tryReleaseShared(arg)) {//唤醒同步队列中的一个线程doReleaseShared();return true;}return false;
}

Semaphore的使用

OK,讲到这里,把信号量中主要的方法解释完了,我们来写一个小demo感受一下它的使用:

【测试用例1】

public class Test {private final Semaphore semaphore;/*构造一个令牌*/public Test(int acq){this.semaphore= new Semaphore(acq);}public void useSemaphore(){try {semaphore.acquire();// 使用资源System.out.println("资源开始使用了 " + Thread.currentThread().getName());Thread.sleep(1000); // 模拟资源使用时间} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release();System.out.println("资源释放了 " + Thread.currentThread().getName());}}public static void main(String[] args) {Test test = new Test(3);for (int i = 0; i < 5; i++) {new Thread(test::useSemaphore).start();}}
}

输出:

资源开始使用了 Thread-0
资源开始使用了 Thread-1
资源开始使用了 Thread-3
资源释放了 Thread-0
资源开始使用了 Thread-2
资源开始使用了 Thread-4
资源释放了 Thread-1
资源释放了 Thread-3
资源释放了 Thread-4
资源释放了 Thread-2

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

在这里插入图片描述
如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

在这里插入图片描述

这篇关于今天我们来聊一聊Java中的Semaphore的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/901251

相关文章

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

22.手绘Spring DI运行时序图

1.依赖注入发生的时间 当Spring loC容器完成了 Bean定义资源的定位、载入和解析注册以后,loC容器中已经管理类Bean 定义的相关数据,但是此时loC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况 发生: 、用户第一次调用getBean()方法时,loC容器触发依赖注入。 、当用户在配置文件中将<bean>元素配置了 lazy-init二false属性,即让