JUC 中提供的限流利器-Semaphore(信号量)
在 JUC 包下,有一个 Semaphore 类,翻译成信号量,Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。Semaphore 跟锁(synchronized、Lock)有点相似,不同的地方是,锁同一时刻只允许一个线程访问某一资源,而 Semaphore 则可以控制同一时刻多个线程访问某一资源。
Semaphore(信号量)并不是 Java 语言特有的,几乎所有的并发语言都有。所以也就存在一个信号量模型的概念,如下图所示:
信号量模型比较简单,可以概括为:一个计数器、一个队列、三个方法。
计数器:记录当前还可以运行多少个资源访问资源。
队列:待访问资源的线程
三个方法:
- init():初始化计数器的值,可就是允许多少线程同时访问资源。
- up():计数器加1,有线程归还资源时,如果计数器的值大于或者等于 0 时,从等待队列中唤醒一个线程
- down():计数器减 1,有线程占用资源时,如果此时计数器的值小于 0 ,线程将被阻塞。
这三个方法都是原子性的,由实现方保证原子性。例如在 Java 语言中,JUC 包下的 Semaphore 实现了信号量模型,所以 Semaphore 保证了这三个方法的原子性。
Semaphore 是基于 AbstractQueuedSynchronizer 接口实现信号量模型的。AbstractQueuedSynchronizer 提供了一个基于 FIFO 队列,可以用于构建锁或者其他相关同步装置的基础框架,利用了一个 int 来表示状态,通过类似 acquire 和 release 的方式来操纵状态。关于 AbstractQueuedSynchronizer 更多的介绍,可以点击链接:
AbstractQueuedSynchronizer 在 Semaphore 类中的实现类如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
在 Semaphore 类中,实现了两种信号量:公平的信号量和非公平的信号量,公平的信号量就是大家排好队,先到先进,非公平的信号量就是不一定先到先进,允许插队。非公平的信号量效率会高一些,所以默认使用的是非公平信号量。具体的可以查看 Semaphore 类实现源码。
Semaphore 类中,主要有以下方法:
// 构造方法,参数表示许可证数量,用来创建信号量
public Semaphore(int permits);
// 从信号量中获取许可,相当于获取到执行权
public void acquire() throws InterruptedException;
// 尝试获取1个许可,不管是否能够获取成功,都立即返回,true表示获取成功,false表示获取失败
public boolean tryAcquire();
// 将许可还给信号量
public void release();
Semaphore 类的实现就了解的差不多了。可能你会有疑问 Semaphore 的应用场景是什么?Semaphore 可以用来限流(流量控制),在一些公共资源有限的场景下,Semaphore 可以派上用场。比如在做日志清洗时,可能有几十个线程在并发清洗,但是将清洗的数据存入到数据库时,可能只给数据库分配了 10 个连接池,这样两边的线程数就不对等了,我们必须保证同时只能有 10 个线程获取数据库链接,否则就会存在大量线程无法链接上数据库。
用 Semaphore 信号量来模拟这操作,代码如下:
public class SemaphoreDemo {
/**
* semaphore 信号量,可以限流
*
* 模拟并发数据库操作,同时有三十个请求,但是系统每秒只能处理 5 个
*/
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors
.newFixedThreadPool(THREAD_COUNT);
// 初始化信号量,个数为 5
private static Semaphore s = new Semaphore(5);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
// 获取许可
s.acquire();
System.out.println(Thread.currentThread().getName()+" 完成数据库操作 ,"+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format( new Date()));
// 休眠两秒钟,效果更直观
Thread.sleep(2000);
// 释放许可
s.release();
} catch (InterruptedException e) {
}
}
});
}
// 关闭连接池
threadPool.shutdown();
}
}
运行效果如下:
从结果中,可以看出,每秒只有 5 个线程在执行,这符合我们的预期。
好了,关于 Semaphore 的内容就结束了,更加详细的还请您查阅相关资料和阅读 Semaphore 源码。希望这篇文章对您的学习或者工作有所帮助。
感谢您的阅读,祝好。
最后
目前互联网上很多大佬都有 Semaphore(信号量) 相关文章,如有雷同,请多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。
JUC 中提供的限流利器-Semaphore(信号量)的更多相关文章
- 管道限流利器pv
pv 是什么 可不是 page view,是pipe viewer,管道偷窥器的缩写.这个东西的源站点在google code上,需要的话可以访问pv 的官网 . 这个东西的官方手册页(man pv或 ...
- 高并发之API接口限流
在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再 ...
- java并发系列 - 第29天:高并发中常见的限流方式
这是java高并发系列第29篇. 环境:jdk1.8. 本文内容 介绍常见的限流算法 通过控制最大并发数来进行限流 通过漏桶算法来进行限流 通过令牌桶算法来进行限流 限流工具类RateLimiter ...
- 高并发之 API 接口,分布式,防刷限流,如何做?
在开发分布式高并发系统时有三把利器用来保护系统:缓存.降级.限流 缓存 缓存的目的是提升系统访问速度和增大系统处理容量 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解 ...
- coding++:RateLimiter 限流算法之漏桶算法、令牌桶算法--简介
RateLimiter是Guava的concurrent包下的一个用于限制访问频率的类 <dependency> <groupId>com.google.guava</g ...
- Spring Cloud Zuul 限流详解(附源码)(转)
在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud中如何实现限流. 在 Zuul 上实现限流是个不错的选择,只需要编写一个过滤器就可以了,关键在于如何实现限流的算法. ...
- Spring Cloud限流详解
转自:https://blog.csdn.net/tracy38/article/details/78685707 在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud ...
- Spring Cloud限流思路及解决方案
转自: http://blog.csdn.net/zl1zl2zl3/article/details/78683855 在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Clo ...
- Zuul:构建高可用网关之多维度限流
对请求的目标URL进行限流(例如:某个URL每分钟只允许调用多少次) 对客户端的访问IP进行限流(例如:某个IP每分钟只允许请求多少次) 对某些特定用户或者用户组进行限流(例如:非VIP用户限制每分钟 ...
随机推荐
- Jackie's blog
介绍使用winmm.h进行音频流的获取 首先需要包含以下引用对象 #include <Windows.h>#include "mmsystem.h"#pragma ...
- 阿里大数据竞赛season1 总结
关于样本测试集和训练集数量上,一般是选择训练集数量不小于测试集,也就是说训练集选取6k可能还不够,大家可以多尝试得到更好的效果: 2. 有人提出归一化方面可能有问题,大家可以查查其他的归一化方法,但是 ...
- HTML 和 CSS 基础
Contents hyper text markup language 超文本标记语言,是最基础的网页开发语言.网页文件后缀名以.html/.htm结束. 基本标签 文件标签 <!DOCTYPE ...
- LeetCode~941.有效的山脉数组
941.有效的山脉数组 给定一个整数数组 A,如果它是有效的山脉数组就返回 true,否则返回 false. 让我们回顾一下,如果 A 满足下述条件,那么它是一个山脉数组: A.length > ...
- STL迭代器的使用、正向、逆向输出双向链表中的所有元素
*/ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:text.cpp * 作者:常轩 * 微信公众号:Worldhe ...
- 死磕Lambda表达式(二):Lambda的使用
城市就是森林,每一个男人都是猎手,每一个女人都是陷阱.--<三体> 在哪使用Lambda表达式? 在上一篇文章(传送门)中介绍了Lambda表达式的基本语法,其中的举了一个Lambda表达 ...
- node--非阻塞式I/O,单线程,异步,事件驱动
1.单线程 不同于其他的后盾语言,node是单线程的,大大节约服务器开支 node不为每个客户创建一个新的线程,仅使用一个线程.通过非阻塞I/O以及 事件驱动机制,使其宏观上看是并发的,可以处理高并发 ...
- canvas初尝试
最近学习了canvas,就拿它做了这么个小东西,感觉已经爱上canvas了.上代码 /* * @auhor : 开发部-前端组-李鑫超 * @property { tableData : {Array ...
- nsq 初学使用日记
win下更加直观一些,所以不使用liunx 第一步下载 nsq 下载地址 https://github.com/nsqio/nsq.git 使用git clone或者go get 下载下来 第二部 编 ...
- 小白学 Python 数据分析(17):Matplotlib(二)基础操作
人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析(1):数据分析基础 小白学 Python 数据分析(2):Pandas (一)概述 小白学 Python 数据分析(3):P ...