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. 使用WxPusher给自己的个人微信发送提醒消息(WxPusher微信推送服务)

    1.背景 我们很多时候,我们在服务器上运行软件,发生一些业务异常,需要给我们发送一个及时的提醒,或者是使用一些耗时软件,比如抢车票,抢课,刷优惠券当任务运行成功以后,也需要及时的发送消息给自己 ,告诉 ...

  2. 源码都没调试过,怎么能说熟悉 redis 呢?

    一:背景 1. 讲故事 记得在很久之前给初学的朋友们录制 redis 视频课程,当时结合了不少源码进行解读,自以为讲的还算可以,但还是有一个非常核心的点没被分享到,那就是源码级调试, 对,读源码还远远 ...

  3. freopen ()函数

    1.格式 FILE * freopen ( const char * filename, const char * mode, FILE * stream ); 2.参数说明 filename: 要打 ...

  4. LRU算法详解

    一.什么是 LRU 算法 就是一种缓存淘汰策略. 计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新内容腾位置.但问题是,删除哪些内容呢?我们肯定希望删掉哪些没什么用的缓存,而把有用的数据继续留 ...

  5. bash中选择结构、循环结构与break、continue

    if两种选择结构 if 测试条件; then 程序块 else 程序块 fi if 测试条件1; then 程序块 elif 测试条件2; then 程序块 ... elif 程序条件n; then ...

  6. php之cms后台文章管理及显示

    public function index(){ C('TOKEN_ON',false);//关闭表单令牌 读取配置 //查询指定id的栏目信息 $id=I('get.id');//类别ID $top ...

  7. python爬虫03 Urllib库

    Urllib   这可是 python 内置的库 在 Python 这个内置的 Urllib 库中 有这么 4 个模块 request request模块是我们用的比较多的 就是用它来发起请求 所以我 ...

  8. javascript播放带透明通道的mp4动画

    随着互联网的发展,动画效果也在一直更新,从刚开始的flsh动画,cocos骨骼动画,到YY开源的svga动画.最近1年来,带有透明通道的mp4动画被使用的极为广泛,对于app端.github上有开源的 ...

  9. 21迁移python项目到另一台电脑

    在虚拟环境中将第三方库打包到一个TXT文件:1,在项目保存在项目所在目录,文件可以自定义.pip freeze> requirements.txt命令2,将requirements.txt放入另 ...

  10. python_计算器

    import re from functools import reduce # 定义一个只计算两个数的乘法或除法的函数: def multiply_division(exp): if "* ...