欢送来到《并发王者课》,本文是该系列文章中的第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类有两个外围结构:
Semaphore(int num)
Semaphore(int num, boolean fair)
其中,num
示意的是容许访问共享资源的线程数量,而布尔类型的fair
则示意线程期待时是否须要思考偏心。
2. Semaphore的外围办法
acquire()
: 获取许可,如果以后没有可用的许可,将进入阻塞期待状态;tryAcquire()
:尝试获取许可,无论有没有可用的许可,都会立刻返回;release()
: 开释许可;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推送行业深度好文。
如果本文对你有帮忙,欢送点赞、关注、监督,咱们一起从青铜到王者。