Java并发编程实战————Semaphore信号量的使用浅析

引言

本篇博客讲解《Java并发编程实战》中的同步工具类:信号量 的使用和理解。

从概念、含义入手,突出重点,配以代码实例及讲解,并以生活中的案例做类比加强记忆。

什么是信号量

Java中的同步工具类信号量即计数信号量(Counting Semaphore),是用来控制访问某个特定资源的操作数量,或同时执行某个指定操作的数量。可以简单理解为信号量用来限制对某个资源的某种操作的数量。

一般用于实现某种资源池,或对容器施加边界。

 信号量管理着一组有限个数的虚拟许可(permit),而许可的数量就是限制特定操作数量的关键。

信号量的使用

前面已经说过,信号量一般用于实现某种资源池或对容器施加边界,这都是一个对特定操作的限制用途。那么想象一下,如何限制操作的数量,达到为一个再普通不过的容器施加边界的效果呢?答案是给容器的某种操作(可以是添加或删除元素,应该广义的理解“某种操作”这个关键字眼)增加一道执行许可,只有在获得许可的情况下才可以执行这个操作:

上图左边是普通的对容器的操作,右边是有了信号量的对容器的操作。可以看出,在增加了中间的信号量之后,对容器的操作将会受限。

Semaphore

了解了信号量的大概含义,那么进一步深入到Java类库的层面,JDK为开发者提供了java.util.concurrent包下的Semaphore类,它的含义就是上面所述的信号量,管理着一组permit。

以“为容器施加边界”这一信号量用途为例。首先我们要明确一点,使用信号量的方式来实现施加边界的方式,其针对的是操作而不是容器的容量!再一次重申,是限制了操作,而不是容器的容量!

强调限制操作,是为了要明白一点:使用信号量来施加边界,必然会对这个容器的某些操作进一步封装。比如添加方法,就会在调用add之前先行调用Semaphore对象的acquire()方法,在与这个操作相反的操作中去release()。并且,acquire()方法是阻塞式的,这就代表没有闲置许可的时候,操作将会阻塞直到有许可被释放。

下面代码用信号量来对HashSet这个最普通的容器来施加一个添加限制,进一步封装,使其成为一个有界的阻塞式的容器

public class BoundedHashSet<T> {private final Set<T> set;private final Semaphore sem;public BoundedHashSet(int bound) {this.set = Collections.synchronizedSet(new HashSet<>());this.sem = new Semaphore(bound);}public boolean add(T o) throws InterruptedException {sem.acquire();boolean wasAdded = false;try {wasAdded = set.add(o);return wasAdded;} finally {if (!wasAdded)sem.release();}}public boolean remove(Object o) {boolean wasRemoved = set.remove(o);if (wasRemoved)sem.release();return wasRemoved;}/** 只是为了方便打印的 */public void print() {System.out.print(Thread.currentThread().getName() + " : ");this.set.forEach(o -> System.out.print(o + " "));}/** 用于测试的主方法 */public static void main(String[] args) {BoundedHashSet<String> names = new BoundedHashSet<>(5);new Thread(() -> {for (int i = 0; i < 100; i++) {try {names.add("name" + i);names.print();System.out.println();TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}}}, "TH-ADD").start();new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "--------执行清理,删除name" + i);names.remove("name" + i);try {TimeUnit.SECONDS.sleep(10);} catch (Exception e) {e.printStackTrace();}}},"TH-REMOVE").start();}
}

执行结果如下:

我们用一个线程为这个“有界”容器每隔1秒钟添加一个元素,然后另一个线程每隔10秒钟移除一个元素。且初始化了这个容器的信号量为5,那么当容器中添加元素的数量达到5之后,5个许可全部被占用,添加操作将进入阻塞状态,直到remove的时候释放一个许可,才可以继续添加元素。从上述结果可以看出两点:

1、拥有5个许可的信号量成功的限制了容器的元素个数(即为容器施加了一个边界);

2、添加的操作在没有获得许可的情况下将进入阻塞状态,在执行的过程中也恰恰印证了这一点:当remove执行并release()之后,添加操作会立刻执行。

生活中的类比

其实这个类比博主认为,从严谨的角度来讲,并不是完全符合信号量的概念,但是我们可以类比的同时找出不同点,不仅有效的通过生活案例理解了信号量,还对与之不同的地方增加了深刻的印象,所以还是决定拿出来供大家参考。

上过学的同学可能都知道,学校有奖学金制度。虽然我没怎么得过奖学金,但是大概的逻辑还是比较好理解。

学校的奖学金制度是怎样的呢?

学校每年都会给全校的学生指定数量的全额奖学金名额,比如全额奖学金5名。那么如果想获得全额奖学金,就必须先获得名额才行。

从这个简单的逻辑我们可以找出关键的与信号量中的概念相匹配的内容:

奖学金 = 特定资源

获得(奖学金) = 指定操作(如remove操作)

名额 = 一组定额许可的信号量

名额已满,来年再报 = 操作阻塞,等待释放许可

 有了上面的等式,信号量的神秘面纱就算彻底被我们揭开了,原来它就是一个管理一组定额许可的通行证,要想执行操作,那就必须先得到许可,否则就阻塞。

总结

信号量的概念:限制操作数量。

一个类:Semaphore ,两个方法:acquire()、release()。

用途:对容器施加边界,对容器的操作的再封装。

另外,奖学金和信号量之间的类比并不完全匹配,不过这种程度的类比已经相当清晰,至于哪些信息有所差异,留给各位看官自己去挖掘。如果有什么新的发现,真诚希望在博客下方留言。

愿所有热爱编程的开发者共同进步!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558895.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaCard概述

什么是JavaCard JavaCard&#xff0c;即Java智能卡。以智能卡硬件系统为基础&#xff0c;通过软件的方式构造一个支持Java程序下载、安装、运行的软/硬件系统。由于引入了虚拟机技术&#xff0c;JavaCard具备硬件无关性&#xff0c;即智能卡应用程序开发与智能卡硬件系统相分离…

LeetCode算法入门- Add Two Numbers-day3

LeetCode算法入门- Add Two Numbers-day3 Add Two Numbers You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return …

Java核心篇之多线程---day1

Java面试之多线程—day1 一. 线程中sleep方法与wait方法有什么区别&#xff1f; 对于 sleep()方法&#xff0c;我们首先要知道该方法是属于 Thread 类中的。而 wait()方法&#xff0c;则是属于Object 类中的。 在调用 sleep()方法的过程中&#xff0c; 线程不会释放对象锁。而…

Java 多线程 —— 常用并发容器

引言 本博客基于常用的并发容器&#xff0c;简单概括其基本特性和简单使用&#xff0c;并不涉及较深层次的原理分析和全面的场景用法。 适合对不了解并发容器的同学&#xff0c;工作中遇到类似的场景&#xff0c;能够对文中提到的并发容器留有简单印象就好。 一、Concurrent…

Tomcat运行三种模式:http-bio|http-nio|http-apr介绍

转自《tomcat运行三种模式:http-bio|http-nio|http-apr介绍》 Tomcat是一个小型的轻量级应用服务器&#xff0c;也是JavaEE开发人员最常用的服务器之一。不过&#xff0c;许多开发人员不知道的是&#xff0c;Tomcat Connector(Tomcat连接器)有bio、nio、apr三种运行模式&#…

LeetCode算法入门- Reverse Integer-day6

LeetCode算法入门- Reverse Integer-day6 Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 Output: 321 Example 2: Input: -123 Output: -321 Example 3: Input: 120 Output: 21 class Solution {public int reverse(int x) {long…

Java工具方法——属性拷贝方法:BeanUtils.copyProperties(Object, Object)

介绍 org.springframework.beans.BeanUtils.copyProperties(Object, Object)是spring 框架的对象工具类&#xff1a;BeanUtils下的一个拷贝对象属性的方法。 官方注释 把给定的源对象属性值拷贝到目标对象中。 注意&#xff1a;源对象类与目标对象类不一定非要完全匹配&…

Git初学札记(九)————EGit检出远程分支

引言 现在有这样一个使用场景&#xff1a;团队中的其他开发者提交了一个新的特性分支&#xff08;如feature_1&#xff09;&#xff0c;要求我们一同开发&#xff0c;并将自己修改的代码也全部提交到这个分支上去。那么如何将这个分支检出&#xff0c;并将本地检出的分支与这个…

Spring Boot————ApplicationListener实现逃课事件监听

引言 上一篇文章转了一篇关于ApplicationListener用于在Web项目启动时做一些初始化的用法。 但是&#xff0c;在实际生产过程中&#xff0c;当一个事件产生&#xff0c;又是如何被onApplicationEvent()方法监听到&#xff0c;并执行一系列动作呢&#xff1f;简单搜索了一下&a…

Java核心篇之Redis--day4

Java核心篇之Redis–day4 Redis有哪些数据结构&#xff1f; 字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。 1.String&#xff1a;字符串&#xff0c;常用命令&#xff1a;get&#xff0c;set&#xff0c;decr&#xff0c;incr&#xff0c;mget&#xff08;查…

软件版本GA、RC、beta等含义

原文《软件版本GA、RC、beta等含义》 GA General Availability&#xff0c;正式发布的版本&#xff0c;官方开始推荐广泛使用&#xff0c;国外有的用GA来表示release版本。 RELEASE 正式发布版&#xff0c;官方推荐使用的版本&#xff0c;有的用GA来表示。比如spring。 Sta…

Java核心篇之泛型--day5

Java核心篇之泛型–day5 泛型是JDK5时引入的一个新特性&#xff0c;泛型提供了编译时类型安全检查的机制&#xff0c;该机制允许程序猿在编译时检测到非法的类型输入。 泛型的本质是参数化类型&#xff0c;也就是说操作的类型被指定为一个参数。 假定我们有一个需求&#xff…

Spring Boot————AOP入门案例及切面优先级设置

看了这篇文章&#xff0c;如果你还是不会用AOP来写程序&#xff0c;请你打我&#xff01;&#xff01; .||| 引言 Spring AOP是一个对AOP原理的一种实现方式&#xff0c;另外还有其他的AOP实现如AspectJ等。 AOP意为面向切面编程&#xff0c;是通过预编译方式和运行期动态代…

Spring Boot————Spring Data JPA简介

引言 JPA是Java 持久化API的缩写&#xff0c;是一套Java数据持久化的规范&#xff0c; Spring Data Spring Data项目的目的是为了简化构建基于Spring 框架应用的数据访问技术&#xff0c;包括对关系型数据库的访问支持。另外也包含非关系型数据库、Map-Reduce框架、云数据服…

Spring Boot————Spring Boot启动流程分析

一、引言 Spring Boot 的启动虽然仅仅是执行了一个main方法&#xff0c;但实际上&#xff0c;运行流程还是比较复杂的&#xff0c;其中包含几个非常重要的事件回调机制。在实际生产开发中&#xff0c;有时候也会利用这些启动流程中的回调机制&#xff0c;做一些项目初始化的工…

Spring Boot————应用启动时的监听机制测试

引言 本文承接前面的《Spring Boot————Spring Boot启动流程分析》&#xff0c;主要测试一下ApplicationContextInitializer、SpringApplicationRunListener、ApplicationRunner、CommandLineRunner这四个接口实现之下的组件是何时在Spring Boot项目启动时创建并执行相关方…

2018年度总结

2018年&#xff0c;已经成为过去式&#xff0c;这360多天依旧过的很快&#xff0c;快到当我手扶键盘回想这一年发生的点点滴滴时&#xff0c;都没有任何感慨。可能我天生是个无感之人&#xff0c;或许&#xff0c;这一年的时光&#xff0c;无数的事故、故事已经让我变得不那么感…

Spring Boot————默认缓存应用及原理

引言 应用程序的数据除了可以放在配置文件中、数据库中以外&#xff0c;还会有相当一部分存储在计算机的内存中&#xff0c;这部分数据访问速度要快于数据库的访问&#xff0c;因此通常在做提升数据访问速度时&#xff0c;会将需要提升访问速度的数据放入到内存中&#xff0c;…

LeetCode算法入门- Multiply Strings -day18

LeetCode算法入门- Multiply Strings -day18 题目介绍 Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string. Example 1: Input: num1 “2”, num2 “3” Output: “6” Exampl…

Linux——VMware虚拟机安装CentOS步骤

一、下载CentOS.iso镜像 最地道的下载方式就是通过官网&#xff0c;大多数的网上连接会直接抛出网易、华为的镜像连接&#xff0c;实际上这些连接都可以在官网找到&#xff1a; 官网地址&#xff08;可直接百度搜索CentOS&#xff09;&#xff1a;https://www.centos.org/ 1…