引言

本篇博客讲解《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()。

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

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

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

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

  1. java并发编程实战学习(3)--基础构建模块

    转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put ...

  2. 《Java 并发编程实战》--读书笔记

    Java 并发编程实战 注: 极客时间<Java 并发编程实战>–读书笔记 GitHub:https://github.com/ByrsH/Reading-notes/blob/maste ...

  3. Java并发编程实战笔记2:对象的组合

    设计线程安全的类 在设计现车让安全类的过程之中,需要包含以下三步: 找出构成对象状态的所有变量 找出约束状态变量的不变性条件 建立对象状态的并发访问策略 实例封闭 通过封闭机制与合适的加锁策略结合起来 ...

  4. Java并发编程实战_不愧是领军人物!这种等级的“Java并发编程宝典”谁能撰写?...

    前言 大家都知道并发编程技术就是在同一个处理器上同时的去处理多个任务,充分的利用到处理器的每个核心,最大化的发挥处理器的峰值性能,这样就可以避免我们因为性能而产生的一些问题. 大厂的核心负载肯定是非常 ...

  5. 视频教程-Java并发编程实战-Java

    Java并发编程实战 2018年以超过十倍的年业绩增长速度,从中高端IT技术在线教育行业中脱颖而出,成为在线教育领域一匹令人瞩目的黑马.咕泡学院以教学培养.职业规划为核心,旨在帮助学员提升技术技能,加 ...

  6. 【极客时间】《Java并发编程实战》学习笔记

    目录: 开篇词 | 你为什么需要学习并发编程? 内容来源:开篇词 | 你为什么需要学习并发编程?-极客时间 例如,Java 里 synchronized.wait()/notify() 相关的知识很琐 ...

  7. aqs clh java_【Java并发编程实战】—– AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...

  8. java 多线程缓存_[Java教程]【JAVA并发编程实战】12、使用condition实现多线程下的有界缓存先进先出队列...

    [Java教程][JAVA并发编程实战]12.使用condition实现多线程下的有界缓存先进先出队列 0 2016-11-29 17:00:10 package cn.study.concurren ...

  9. Java并发编程实战————恢复中断

    中断是一种协作机制,一个线程不能强制其他线程停止正在执行的操作而去执行其他操作. 什么是中断状态? 线程类有一个描述自身是否被中断了的boolean类型的状态,可以通过调用 .isInterrupte ...

最新文章

  1. 干货 | VMAF视频质量评估在视频云转码中的应用
  2. python画图保存成html格式、用浏览器打开页面为空白_解决pyecharts运行后产生的html文件用浏览器打开空白...
  3. StackExchange.Redis通用封装类分享(转)
  4. random---伪随机数生成器
  5. rac一节点时间比另一个节点快_数据库数据那么多为什么可以检索这么快?
  6. SpringBoot2.x Nacos RocketMQ 事务消息
  7. grafana的+按钮_基于 Prometheus、Grafana 的 EMQ X 物联网 MQTT 服务器可视化运维监控...
  8. Linux 命令(130)—— userdel 命令
  9. 云鹊医怎么快速认证_兴趣认证怎么申请?掌握这9个小技巧,快速通过
  10. 黑马程序员机器学习Day1学习笔记
  11. [转]企业安全建设二——如何推动安全策略
  12. 【数据可视化】免费开源BI工具DataEase实现了SQL数据集和Excel数据集关联?(什么?快别挡着我,冲!)
  13. 面向接口编程与面向实现编程
  14. 面试:如何评价程序员
  15. Android IBeacon
  16. 2022危险化学品经营单位主要负责人考试题库及答案
  17. duilib适配高分屏(高DPI适配)
  18. python 常微分方程 画向量场_用scipy-odein在python中求解向量常微分方程
  19. python之Scapy 中文文档:三、使用方法
  20. Eigen/Matlab库矩阵运算方法

热门文章

  1. ruby 类方法与实例方法_Ruby Set相交? 实例方法
  2. Java Long类的valueOf()方法及示例
  3. 【faster rcnn 实现via的自动框人】使用detectron2中faster rcnn 算法生成人的坐标,将坐标导入via(VGG Image Annotator)中,实现自动框选出人的区域
  4. 手把手带你玩转Tensorflow 物体检测 API (2)——数据准备
  5. linux 日记函数,每日记一些php函数,jQuery函数和linux命令(一)
  6. lisp医院化验系统_医院智能导视系统
  7. 湖北经济学院的计算机怎么样,湖北经济学院怎么样名气高吗?真实排名及实力如何?是一本吗...
  8. 大学计算机课程考试系统C语言题库,《大学计算机基础》考试题库(含答案).doc...
  9. oracle删除唯一索引sql语句_高级SQL之在选择语句中使用更新和删除
  10. go语言 mysql卡死_一次mysql死锁的排查过程-Go语言中文社区