前言
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。
本文我们来学习下信号量的相关用法及实战。
Semaphore信号量简介
从图中可以看出,信号量的一个最主要的作用就是,来控制那些需要限制并发访问量的资源。具体来讲,信号量会维护“许可证”的计数,而线程去访问共享资源前,必须先拿到许可证。线程可以从信号量中去“获取”一个许可证,一旦线程获取之后,信号量持有的许可证就转移过去了,所以信号量手中剩余的许可证要减一。
同理,线程也可以“释放”一个许可证,如果线程释放了许可证,这个许可证相当于被归还给信号量了,于是信号量中的许可证的可用数量加一。当信号量拥有的许可证数量减到 0 时,如果下个线程还想要获得许可证,那么这个线程就必须等待,直到之前得到许可证的线程释放,它才能获取。由于线程在没有获取到许可证之前不能进一步去访问被保护的共享资源,所以这就控制了资源的并发访问量,这就是整体思路。
场景
在这个场景中,我们的服务是中间这个方块儿,左侧是请求,右侧是我们所依赖的那个慢服务。出于种种原因(比如计算量大、依赖的下游服务多等),右边的慢服务速度很慢,并且它可以承受的请求数量也很有限,一旦有太多的请求同时到达它这边,可能会导致它这个服务不可用,会压垮它。所以我们必须要保护它,不能让太多的线程同时去访问。那怎么才能做到这件事情呢?
在讲解怎么做到这个事情之前,我们先来看一看,在通常的场景下,我们用一个普通线程池能不能做到这件事情。
package com.concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SemaphoreDemo1 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (int i = 0; i < 1000; i++) {
executorService.submit(new Task());
}
executorService.shutdown();
}
static class