Semaphore (JDK)

我们使用semaphore去限制获取特定资源的并发线程数量。

下面的例子中,我们实现了一个简单的登录队列来限制登入系统的用户数量:

class LoginQueueUsingSemaphore {

    private Semaphore semaphore;

    public LoginQueueUsingSemaphore(int slotLimit) {
semaphore = new Semaphore(slotLimit);
} boolean tryLogin() {
return semaphore.tryAcquire();
} void logout() {
semaphore.release();
} int availableSlots() {
return semaphore.availablePermits();
}
}

注意下我们使用这些方法的方式:

  • tryAcquire():如果还有可用的permit(构造方法传入的,表示限制的线程数量)则立即返回true,否则返回false,但是acquire()方法会以阻塞的方式获取一个permit。
  • release():释放一个permit。
  • availablePermits():返回当前可用的permit数量。

我们来测试一下我们的登录队列,我们首先使用完所有的permit,然后再获取一个看看是否会被阻塞:

@Test
public void givenLoginQueue_whenReachLimit_thenBlocked() {
int slots = 10;
ExecutorService executorService = Executors.newFixedThreadPool(slots);
LoginQueueUsingSemaphore loginQueue = new LoginQueueUsingSemaphore(slots);
IntStream.range(0, slots)
.forEach(user -> executorService.execute(loginQueue::tryLogin));
executorService.shutdown(); assertEquals(0, loginQueue.availableSlots());
assertFalse(loginQueue.tryLogin());
}

现在我们logout()一下看看是否有可用的permit:

@Test
public void givenLoginQueue_whenLogout_thenSlotsAvailable() {
int slots = 10;
ExecutorService executorService = Executors.newFixedThreadPool(slots);
LoginQueueUsingSemaphore loginQueue = new LoginQueueUsingSemaphore(slots);
IntStream.range(0, slots)
.forEach(user -> executorService.execute(loginQueue::tryLogin));
executorService.shutdown();
assertEquals(0, loginQueue.availableSlots());
loginQueue.logout(); assertTrue(loginQueue.availableSlots() > 0);
assertTrue(loginQueue.tryLogin());
}

结果显而易见。

Timed Semaphore (Apache Commons)

我们现在看看ApacheCommons下实现的TimedSemaphore。TimedSemaphore允许在既定的时间内维护一定数量的Semaphore(这段时间内和JDK实现的Semaphore效果一样),当时间过去后会释放所有的permits。

我们可以使用TimedSemaphore来构建一个简单的延时队列:

class DelayQueueUsingTimedSemaphore {

    private TimedSemaphore semaphore;

    DelayQueueUsingTimedSemaphore(long period, int slotLimit) {
semaphore = new TimedSemaphore(period, TimeUnit.SECONDS, slotLimit);
} boolean tryAdd() {
return semaphore.tryAcquire();
} int availableSlots() {
return semaphore.getAvailablePermits();
}
}

现在我们设置超时时间1秒,在1秒钟之内使用完所有的permit再次尝试获取的时候就会没有可用的permit:

public void givenDelayQueue_whenReachLimit_thenBlocked() {
int slots = 50;
ExecutorService executorService = Executors.newFixedThreadPool(slots);
DelayQueueUsingTimedSemaphore delayQueue
= new DelayQueueUsingTimedSemaphore(1, slots); IntStream.range(0, slots)
.forEach(user -> executorService.execute(delayQueue::tryAdd));
executorService.shutdown(); assertEquals(0, delayQueue.availableSlots());
assertFalse(delayQueue.tryAdd());
}

但是把线程休眠1秒后,这时候semaphore会重置并释放所有的permits

@Test
public void givenDelayQueue_whenTimePass_thenSlotsAvailable() throws InterruptedException {
int slots = 50;
ExecutorService executorService = Executors.newFixedThreadPool(slots);
DelayQueueUsingTimedSemaphore delayQueue = new DelayQueueUsingTimedSemaphore(1, slots);
IntStream.range(0, slots)
.forEach(user -> executorService.execute(delayQueue::tryAdd));
executorService.shutdown(); assertEquals(0, delayQueue.availableSlots());
Thread.sleep(1000);
assertTrue(delayQueue.availableSlots() > 0);
assertTrue(delayQueue.tryAdd());
}

Semaphore vs. Mutex

Mutex像是一个二进制的Semaphore,我们可以使用它来实现互斥。

在下面的这个例子中,我们使用一个permit为1的Semaphore来构建一个计数器:

class CounterUsingMutex {

    private Semaphore mutex;
private int count; CounterUsingMutex() {
mutex = new Semaphore(1);
count = 0;
} void increase() throws InterruptedException {
mutex.acquire();
this.count = this.count + 1;
Thread.sleep(1000);
mutex.release(); } int getCount() {
return this.count;
} boolean hasQueuedThreads() {
return mutex.hasQueuedThreads();
}
}

当大量线程同时来操作counter的时候,他们都会在队列中阻塞:

@Test
public void whenMutexAndMultipleThreads_thenBlocked()
throws InterruptedException {
int count = 5;
ExecutorService executorService
= Executors.newFixedThreadPool(count);
CounterUsingMutex counter = new CounterUsingMutex();
IntStream.range(0, count)
.forEach(user -> executorService.execute(() -> {
try {
counter.increase();
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
executorService.shutdown(); assertTrue(counter.hasQueuedThreads());
}

我们把线程休眠一会后,所有的线程都将能操作counter,这时队列中就没有等待排队的线程了。

@Test
public void givenMutexAndMultipleThreads_ThenDelay_thenCorrectCount()
throws InterruptedException {
int count = 5;
ExecutorService executorService
= Executors.newFixedThreadPool(count);
CounterUsingMutex counter = new CounterUsingMutex();
IntStream.range(0, count)
.forEach(user -> executorService.execute(() -> {
try {
counter.increase();
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
executorService.shutdown(); assertTrue(counter.hasQueuedThreads());
Thread.sleep(5000);
assertFalse(counter.hasQueuedThreads());
assertEquals(count, counter.getCount());
}

CodeRepo

完整代码在这

JUC并发工具包之Semaphore的更多相关文章

  1. java架构之路(多线程)JUC并发编程之Semaphore信号量、CountDownLatch、CyclicBarrier栅栏、Executors线程池

    上期回顾: 上次博客我们主要说了我们juc并发包下面的ReetrantLock的一些简单使用和底层的原理,是如何实现公平锁.非公平锁的.内部的双向链表到底是什么意思,prev和next到底是什么,为什 ...

  2. JUC并发工具包之CyclicBarrier & CountDownLatch的异同

    1.介绍 本文我们将比较一下CyclicBarrier和CountDownLatch并了解两者的相似与不同. 2.两者是什么 当谈到并发,将这两者概念化的去解释两者是做什么的,这其实是一件很有挑战的事 ...

  3. JUC并发工具包之CountDownLatch

    1.介绍 本文将介绍CountDownLatch并给出实践中的几个例子,通过使用CountDownLatch我们可以让一个线程阻塞直到其他一个或多个线程执行完成. A synchronization ...

  4. JUC并发工具包之CyclicBarrier

    1.简介 CyclicBarrier是一个同步器,允许多个线程等待彼此直到达一个执行点(barrier). CyclicBarrier都是在多个线程必须等到彼此都到达同一个执行点后才执行一段逻辑时才被 ...

  5. Java 语言特性【一】——JUC(Java 并发工具包)

    引言 JUC即java.util.concurrent,是java提供的用于多线程处理的工具类库.重点关注 ConcurrentXXX.AtomicXXX.Executor.Caller&&a ...

  6. Java 并发工具包 java.util.concurrent 用户指南

    1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ...

  7. Java并发编程-并发工具包(java.util.concurrent)使用指南(全)

    1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ...

  8. Java 8并发工具包漫游指南

    Java 8并发工具包简介 Java 8并发工具包由3个包组成,分别是java.util.concurrent.java.util.concurrent.atomic和java.util.concur ...

  9. Java_并发工具包 java.util.concurrent 用户指南(转)

    译序 本指南根据 Jakob Jenkov 最新博客翻译,请随时关注博客更新:http://tutorials.jenkov.com/java-util-concurrent/index.html.本 ...

随机推荐

  1. Flutter - 自定义Dialog弹窗

    ------------恢复内容开始------------ Flutter - 自定义Dialog弹窗 应用场景:app系统版本升级弹窗,系统退出登录弹窗,首页广告弹窗,消息中心弹窗,删除文件弹窗等 ...

  2. origin添加两个Y轴

    1. 选中X和两个Y 2. 点击Double Y 3. 关掉gap to Symbol,否则Line+Symbol这种显示方式可能显示不出线条 ​

  3. IDEA上运行Flink任务

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. Jenkins部署分支报Finished: UNSTABLE的问题解决

    近期将代码分支部署到Jenkins上时报Finished: UNSTABLE,检查服务器无报错日志.怀疑是磁盘空间不足所致. 首先,在服务器上输入df -lh 查看本地磁盘使用情况,发现空间几乎被用完 ...

  5. Jenkins自动化构建PHP实列教程

    安装Jenkins 请参考群主的安装教程 进入jenkins,添加SSH server,并且安装gitlab,Generic Webhook Trigger Plugin,GitHub plugin, ...

  6. 微信小程序日历签到

    近日做了一个项目需要用到日历插件,在网上找了一部分感觉跟项目不对口,所以就查考了其他的日历插件做了一个. 需求: 如图: 代码如下: index.wxml: <!--pages/pictrues ...

  7. STM32入门系列-STM32最小系统介绍

    STM32最小系统组成 单片机最小系统,也就是能够使得单片机正常运行程序,最少需要连接哪些器件.一般来说,STM32最小系统由四部分组成: 电源电路 复位电路 晶振电路 下载电路 STM32单片机由A ...

  8. 【Flutter 混合开发】添加 Flutter 到 iOS

    Flutter 混合开发系列 包含如下: 嵌入原生View-Android 嵌入原生View-iOS 与原生通信-MethodChannel 与原生通信-BasicMessageChannel 与原生 ...

  9. [Luogu P1268] 树的重量 (巧妙的构造题)

    题面 传送门:https://www.luogu.org/problemnew/show/P1268 Solution 这是一道极其巧妙的构造题 先做一个约定[i,j]表示从i到j的距离 我们可以先从 ...

  10. 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)

    再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...