Semaphore的概念及基本用法

Semaphore的概念及基本用法_第1张图片

Semaphore的概念及基本用法

Semaphore这个英文单词的意思是信号灯,即发送信号的那种灯。Java并发工具包中的Semaphore类则是线程之间互相发送信号的工具。我们可以把Semaphore看作一个包含多个许可(permit)的集合,例如一个代表5个许可的Semaphore、6个许可的Semaphore等等(为便于表达,后文用字母P表示许可)。Semaphore上的acuqire操作申请P,而release操作则产生P,Semaphore可用于追踪可用资源的个数

Semaphore的概念及基本用法_第2张图片

用法1: 基本用法(使用步骤)

1. 在代码中的某个地方创建Semaphore(第2个构造参数是公平策略)

Semaphore semaphore = new Semaphore(5, false);

2. 生产者负责调用Semaphore.release,往Semaphore中增加P。

3. 消费者负责调用Semaphore.acquire,从Semaphore取出P。

用法2: 单机限流用法

Semaphore也可以来实现一个单机限流工具(针对单台机器的线程而言)——即限制同时访问某资源的线程数。实现思路:让1个线程以固定的速度生产P,而让多个线程消费P,这样,消费者线程就能以低于某个上限的速度消费资源,不会导致系统超负荷。

Semaphore的概念及基本用法_第3张图片

用法3: 特殊用法

我们可以创建一个只有1个P的Semaphore,即二元信号量。它的功能与锁类似,但是没有所有权的概念。然后,我们可以在一个线程中进行加锁(acquire),而在另一个线程中执行解锁动作(release),并且负责解锁的线程不需要事先获得这个锁。与之相反,ReentrantLock的加锁和解锁动作都必须在同一个线程中完成

Semaphore的实现原理

首先,Semaphore内部并没有真正保存P,而是只保存了P的个数。其次,Semaphore直接复用了AbstractQueueSyncrhonizer框架的共享模式锁,其acquire和release操作直接调用共享模式的AQS加锁和AQS解锁,没有增加其他逻辑,只不过在加锁和解锁的过程中,是把P的个数存入AQS原子整数。

具体的讲,Semaphore的acquire操作(acquireUninterruptibly操作及tryAcquire操作都与acquire类似)尝试取走1个P,而如果P的个数等于0无法取出就阻塞等待。acquire的全部实现就是直接调用AQS类的
acquireSharedInterruptibly方法,仅1行。

Semaphore的概念及基本用法_第4张图片

Semaphore的release操作也非常简单,直接调用AQS的releaseShared方法。

Semaphore的概念及基本用法_第5张图片

AQS框架的共享模式与独占模式

那么这里的
acquireSharedInterruptibly() 和releaseShared()又分别执行了什么操作呢?其实acquireSharedInterruptibly和releaseShared分别是AQS共享模式的加锁操作和解锁操作,它们与独占模式的加锁操作AbstractQueuedSynchronizer.acquire和解锁操作AbstractQueuedSynchronizer.release的逻辑大体上类似,即实现以下功能点:

  1. 竞争性写入原子变量
  2. 将线程放入CLH队列并阻塞
  3. 释放操作唤醒队列头部的线程

关于AQS的具体实现可以参考这篇文章。这里分析一下Semaphore为什么使用AQS的共享模式而不是独占模式,原因如下:

1. Semaphore中保存了P的个数,当一个线程从Semaphore成功获得P以后,其他线程仍然还可以获得P,独占模式不支持该特性。

2. 当线程调用Semaphore.release(int num)之后,往Semaphore中放入了num个P,可以唤醒多个线程而不是只唤醒1个,所以应该用共享模式。

3. 当线程调用Smaphore.acquire(int num),这个操作可能成功也可能失败,如果成功了,后续的acquire还有可能继续成功获取到P,而这是独占模式不允许的,所以是共享模式。

总之,Semaphore一般用来跟踪资源数量,例如实现限流之类的功能。它相对于AQS来说,基本没增加功能,只是概念上容易让人理解一些。

你可能感兴趣的:(Java,数据,程序员,java,开发语言)