欢迎来到《并发王者课》,本文是该系列文章中的第17篇

在并发编程中,信号量是线程同步的重要工具。在本文中,我将带你认识信号量的概念、用法、种类以及Java中的信号量。

信号量(Semaphore) 是线程间的同步结构,主要用于多线程协作时的信号传递,以及对共享资源的保护、防止竞态的发生等。信号量这一概念听起来比较抽象,然而读完本文你会发现它竟然也是如此通俗易懂且挺有用。

一、认识简单的信号量

虽然信号量的概念很抽象,但理解起来可以很简单。比如下面这幅图,在峡谷对局中,大乔使用大招向哪吒发起了救援,而哪吒在接收到求救信号后前往救援

在救援的过程中,信号无疑是关键的。如果把大乔和哪吒看作两个线程,那么他们在求救、救援过程中的信号就可以看作是信号量用于线程间的同步和通信

接下来,我们写一个简单的信号量,模拟还原刚才的求救和施救的过程。

定义一个求救的信号量,里面包含信号信号发送信号接收

// 求救信号
public class ForHelpSemaphore {private boolean signal = false;public synchronized void sendSignal() {this.signal = true;this.notify();System.out.println("呼救信号已经发送!");}public synchronized void receiveSignal() throws InterruptedException {System.out.println("已经就绪,等待求救信号...");while (!this.signal) {wait();}this.signal = false;System.out.println("求救信号已经收到,正在前往救援!");}
}

再创建两个线程,分别代表大乔和哪吒。

 public static void main(String[] args) {ForHelpSemaphore helpSemaphore = new ForHelpSemaphore();Thread 大乔 = new Thread(helpSemaphore::sendSignal);Thread 哪吒 = new Thread(() -> {try {helpSemaphore.receiveSignal();} catch (InterruptedException e) {e.printStackTrace();}});大乔.start();哪吒.start();}

从运行结果中可以看到,他们通过信号量的机制完成了救援行动。

你看,最简单的信号量就是这样的简单。

二、理解宽泛意义上的的信号量

如果把上面大乔和哪吒救援的例子做个梳理的话,可以发展信号量中的一些关键信息:

  • 共享的资源。比如signal字段是两个线程共享的,它是两个线程协同的基础;
  • 多个线程访问相同的共享资源,并根据资源状态采取行动。比如大乔和哪吒都会读写signal字段,然后采取行动。

基于上面的两点理解,我们可以把信号量抽象为下面这张图所示:

从图中可以看到,多个线程共享一份资源列表,但是资源是有限的。所以,线程之间必然要按照一定的顺序有序地访问资源,并在访问结束后释放资源。没有获得资源的线程,只能等待其他线程释放资源后再次尝试获取

多线程对共享资源的访问过程,也可以用下面这张流程图表示:

如果你能把这两幅图理解了,那么你也就把信号量的机制理解了。而一旦理解了机制,所谓的源码不过只是某种具体的实现。

三、认识不同类型的信号量

根据信号量的机制和应用场景,一般有下面几种不同类型的信号量。

1. 计数型信号量

public class CountingSemaphore {private int signals = 0;public synchronized void take() {this.signals++;this.notify();}public synchronized void release() throws InterruptedException {while (this.signals == 0)wait();This.signals--;}
}

2. 边界型信号量

在计数型信号量中,信号的数量是没有限制的。换句话说,所有的线程都可以发送信号。与此不同的是,在边界型信号量中,通过bound字段增加了信号量的限制。

public class BoundedSemaphore {private int signal = 0;private int bound = 0;public BoundedSemaphore(int upperBound) {this.bound = upperBound;}public void synchronized take() throws InterruptedException {while (this.signal == bound)wait();this.signal++;this.notify++;}public void synchronized release() throws InterruptedException {while (this.signal == 0)wait();this.signal--;}
}

3. 定时型信号量

**定时型(timed)**信号量指的是允许线程在指定的时间周期内才能执行任务。时间周期结束后,定时器将会重置,所有的许可也都会被回收。

4. 二进制型信号量

二进制信号量和计数型信号量类似,但许可的值只有0和1两种。实现二进制型信号量相对也是比较容易的,如果是1就是成功,否则是0就是失败。

四、Java中的信号量

在理解了信号量机制并且也理解它很有用之后,先不用着急实现它。在Java中,已经提供了相应的信号量工具类,即java.util.concurrent.Semaphore。并且,Java中的信号量实现已经比较全面,你不需要再重写它。

1. Semaphore的核心构造

Semaphore类有两个核心构造:

  1. Semaphore(int num)
  2. Semaphore(int num, boolean fair)

其中,num表示的是允许访问共享资源的线程数量,而布尔类型的fair则表示线程等待时是否需要考虑公平。

2. Semaphore的核心方法

  1. acquire(): 获取许可,如果当前没有可用的许可,将进入阻塞等待状态;
  2. tryAcquire():尝试获取许可,无论有没有可用的许可,都会立即返回;
  3. release(): 释放许可;
  4. availablePermits():返回可用的许可数量。

五、如何通过信号量实现锁的能力

在上面的示例中,由于信号量可以用于保护多线程对共享资源的访问,所以直觉你可能会觉得它像一把锁,而事实上信号量确实可以用于实现锁的能力。

比如,借助于边界信号量,我们把线程访问的上线设置为1,那么此时将只有1个线程可以访问共享资源,而这不就是锁的能力嘛!

下面是通过信号量实现锁的一个示例:

BoundedSemaphore semaphore = new BoundedSemaphore(1);
...
semaphore.take();
try {//临界区
} finally {semaphore.release();
}

我们把信号量中的信号数量上限设置为1,代码中的take()就相当于lock(),而release()则相当于unlock()。如此,信号量摇身一变就成了名副其实的锁

小结

以上就是关于信号量的全部内容。在本文中,我们介绍了信号量的概念、运行机制、信号量的几种类型、Java中的信号量实现,以及如果通过信号量实现一把锁。

理解信号量的关键在于理解它的概念,也就是它所要解决的问题和它的方案。在理解概念和机制之后,再去看Java中的源码时,就会发现原来如此,又是队列…

正文到此结束,恭喜你又上了一颗星✨

夫子的试炼

  • 基于对信号量的理解,尝试自己实现一个简单的信号量。

延伸阅读与参考资料

  • Semaphores
  • 《并发王者课》大纲与更新进度总览

最新修订及更好阅读体验

  • 阅读掘金原文

关于作者

关注公众号【技术八点半】,及时获取文章更新。传递有品质的技术文章,记录平凡人的成长故事,偶尔也聊聊生活和理想。早晨8:30推送作者品质原创,晚上20:30推送行业深度好文。

如果本文对你有帮助,欢迎点赞关注监督,我们一起从青铜到王者

铂金04:令行禁止-为何说信号量是线程间的同步利器相关推荐

  1. 线程间通信————同步

          同步 是指多个任务按照约定的先后次序 相互配合完成一件事情 信号量: 由信号量决定 线程是继续执行 还是阻塞等待 信号量代表某种资源 其值表示系统中该资源的数量 信号量是一个受保护的量 只 ...

  2. 用信号量实现任务间单向同步

    文章目录 1 用信号量实现任务间单向同步 1.1 问题需求 1.2 解决方案 1.3 其它方案 1.4 典型案例 1 用信号量实现任务间单向同步 1.1 问题需求 某一任务必须等待另一任务允许后才能继 ...

  3. Python | threading03 - 使用条件对象,实现线程间的同步

    文章目录 一.前言 二.生产者-消费者的模型 2.1.代码 2.2.运行 2.3.wait( )方法会将互斥锁释放 三.条件同步 - threading.Condition( ) 3.1.相关API ...

  4. 用于线程间的同步与互斥-信号量sem

    一.线程 首先我们说一下什么是线程.线程是计算机中独立运行的最小单位,在运行时占用很少的系统资源,由于每个线程占用的CPU时间是由系统分配的,因此我们可以把线程看作为系统分配CPU时间的基本单位.在我 ...

  5. Linux c线程间的同步----互斥锁、条件变量、信号量

    线程 一个进程中的所有线程共享为进程分配的地址空间.所以进程地址空间中的代码段和数据段都是共享的. 如果定义一个函数在各个线程中都可以调用,定义一个全部变量,在各个线程中都可以访问到. 各线程共享资源 ...

  6. C++信号量实现线程间同步,windows使用SetEvent,linux使用sem_t,QT测试

    目录 windows使用CreateEvent.SetEvent.ResetEvent.WaitForSingleObject linux使用sem_init.sem_wait.sem_trywait ...

  7. 进程/线程间的同步方式

    目录 1 临界区 1.1 解决冲突的办法 2 互斥量 3 信号量 4 事件 5 总结 1 临界区 通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问. 每个进程中访问临界资源的那段 ...

  8. linux线程同步互斥说法,linux线程间的同步与互斥知识点总结

    在线程并发执行的时候,我们需要保证临界资源的安全访问,防止线程争抢资源,造成数据二义性. 线程同步: 条件变量 为什么使用条件变量? 对临界资源的时序可控性,条件满足会通知其他等待操作临界资源的线程, ...

  9. 线程间通信——信号量

    一.信号量 信号量就是操作系统中所用到的PV原子操作,它广泛用于进程或线程间的同步与互斥.信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问. PV原子操作主要用于进程或线程间的同步和互 ...

最新文章

  1. 【转】OpenStack和Docker、ServerLess能不能决定云计算胜负吗?
  2. (转)数据挖掘——我们能从股市数据得出什么,以及一些算法
  3. 今天你写控件了吗?----ASP.net控件开发系列(三)
  4. 在windows下安装flex和bison、GCC
  5. 结构体里有指针 scanf赋值_C++|链表中常见的链表节点指针操作
  6. 如何解决90%的NLP问题:逐步指导
  7. Query-digest-UI监控慢查询,以及此工具的改进版
  8. CF643F-Bears and Juice【组合数学】
  9. 【OCR技术系列之八】端到端不定长文本识别CRNN代码实现
  10. C++函数编译原理和成员函数的实现
  11. 云中漫步 - 3:2013-4-27 微软云体验营北京站
  12. sklearn模型支持输入list吗?
  13. Spring : @Value注解
  14. vue地址栏输入路由跳转到首页_Vue路由跳转到新页面时 默认在页面最底部 而不是最顶部 的解决...
  15. SWPUACM第二次周赛
  16. 随手记_搞科研怎样读论文
  17. C语言课程设计–成绩管理系统
  18. Windows 10 下载官方正版ISO镜像文件
  19. Session的活化与钝化
  20. Java 图片转换base64

热门文章

  1. 百度地图API显示多个标注点的代码 以及修改传参
  2. 数学建模学习(10):函数全解,很重要!!所以我再仔细讲一遍!新手建议收藏。
  3. Leetcode 312. 戳气球
  4. 电子计算机与多媒体的课文简析,电子计算机与多媒体课文教案
  5. oppor15更新android,OPPOR15 Android P体验 刘海屏体验新方式
  6. 计算机截屏法律措施,电脑屏幕截屏【处置方法】
  7. jbig2转其它格式(支持png,pbm)
  8. nsq命令行参数解析
  9. 实战:利用pytorch搭建VGG-16实现从数据获取到模型训练的猫狗分类网络
  10. DELL Vostro 1000键盘输入反应慢的问题