Java 并发编程Semaphore的应用与源码解析
What
Semaphore标识信号量,允许指定数量的线程同时访问某个资源
How
通过以下两部实现信号量:
acquire
方法用于获得准入许可(如果没有获得许可,则进行等待,直到有线程释放许可而获得许可为止)release
用于释放准入许可
应用场景
- 实现某种资源池限制,类似于数据库连接池
- 对容器施加边界,比如一个集合中最多只能添加5个元素
- 资源并发访问数量限制
- 当作普通的锁使用(信号量为1时相当于普通的锁 信号量大于1时共享锁)
Semaphore代码示例
import java.util.concurrent.Semaphore;public class SemaphoreDemo implements Runnable {Semaphore semaphore = new Semaphore(5);public static void main(String[] args) {SemaphoreDemo semaphoreDemo = new SemaphoreDemo();for (int i = 0; i < 10; i++) {Thread thread = new Thread(semaphoreDemo);thread.start();}}@Overridepublic void run() {try {// 获得准入许可(如何没有获得成功,则进行等待,直到有线程释放许可而获得该许可为止)semaphore.acquire();Thread.sleep(1000);System.out.println(System.currentTimeMillis() + ", " + Thread.currentThread().getName() + ", 执行完毕!");// 释放准入许可semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}
}
示例中,有10条线程对信号量为5的资源进行争用,但每次只有5个线程拿到许可,另外的线程需要等待拿到许可的线程释放许可后才能拿到许可
Semaphore源码解析
关键方法如下:
- 构造方法:new Semaphore(5);
- 获取许可:semaphore.acquire();
- 释放许可:semaphore.release();
接下来我们从这三个方法入手进行源码解析
1. 构造方法:new Semaphore(5)
public Semaphore(int permits) {sync = new NonfairSync(permits);}public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);}
默认构造方法为非公平共享锁,可以通过构造参数fair
来选择公平或非公平,类似于ReentantLock
2. 获取许可:semaphore.acquire()
public void acquire() throws InterruptedException {// 共享式获取AQS的同步状态sync.acquireSharedInterruptibly(1);}
调用的是AQS的acquireSharedInterruptibly
方法:
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted()) {throw new InterruptedException();}if (tryAcquireShared(arg) < 0) {doAcquireSharedInterruptibly(arg);}}
其中tryAcquireShared
依赖于Sync实现,在Semaphore中有AQS的实现Sync类,方法如下:
// 尝试获取共享锁protected int tryAcquireShared(int acquires) {for (; ; ) {// 队列中存在等待线程则返回-1if (hasQueuedPredecessors())return -1;int available = getState(); // 可用许可数量int remaining = available - acquires; // 剩余许可数量if (remaining < 0 || compareAndSetState(available, remaining))// 返回可用的余量return remaining;}}
这是FairSync的tryAcquireShared方法,在NonfairSync中,没有hasQueuedPredecessors()
判断,其余一样。
在方法中可以看出,最终返回的是剩余的许可数量,有如下几种情况:
如果剩余许可数量<0,则执行
doAcquireSharedInterruptibly
方法让线程自旋等待,这里是等待别的线程释放许可后线程被唤醒去尝试获取private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {// 线程进入同步队列final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (; ; ) { // 自旋final Node p = node.predecessor();if (p == head) { // 当前节点的前置节点是AQS的头节点 即自己是AQS同步队列的第一个节点int r = tryAcquireShared(arg); // 再去获取信号量if (r >= 0) {setHeadAndPropagate(node, r); // 退出自旋p.next = null; // help GCfailed = false;return;}}// 判断是否应该挂起该线程if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) {throw new InterruptedException();}}} finally {if (failed) {cancelAcquire(node); // 获取失败 就取消获取}}}
否则就是拿到了许可数量,继续正常执行,不阻塞
3. 释放许可:semaphore.release()
public void release() {sync.releaseShared(1);}
同样,调用的是AQS的releaseShared
方法,看下代码:
public final boolean releaseShared(int arg) {// 调用AQS实现类的tryReleaseSharedif (tryReleaseShared(arg)) {// 唤醒后续的线程节点doReleaseShared();return true;}return false;}
tryReleaseShared
交由子类Sync实现,代码如下:
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)) // CAS更新当前信号量许可数return true;}}
释放许可成功则继续调用AQS的doReleaseShared
方法来唤醒后续节点可以来争取许可了
private void doReleaseShared() {for (; ; ) { // 自旋等待Node h = head;// 有头节点且头节点和尾节点不是同一个if (h != null && h != tail) {int ws = h.waitStatus;if (ws == Node.SIGNAL) {// 设置status为0if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {continue; // 循环检查}// 唤醒节点的后续节点unparkSuccessor(h);} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) { //continue; // 失败则继续循环}}if (h == head) {break;}}}
总结
Semaphore使用AQS同步状态来保存信号量的计数器。
acquireSharedInterruptibly
会减少计数(获取许可),当计数为非正值的时候阻塞线程,否则不会阻塞线程
releaseShared
方法会增加计数(释放许可),在计数不超过信号量限制时会解除线程的阻塞(获取到许可的线程)
Java 并发编程Semaphore的应用与源码解析相关推荐
- Java 并发编程CyclicBarrier的应用与源码解析(基于ReentrantLock实现)
什么是CyclicBarrier? CyclicBarrie和上一篇中讲到CountDownLatch很类似,它能阻塞一组线程直到某个事件的发生. 栅栏与闭锁的关键区别在于:所有必须同时到达栅栏位置才 ...
- Java 并发编程CountDownLatch的应用与源码解析
应用场景 CountDownLatch是一个多线程控制工具.用来控制线程的等待. 设置需要countDown的数量,然后每一个线程执行完毕后调用countDown()方法,而在主线程中调用await( ...
- Java并发编程与技术内幕:ConcurrentHashMap源码解析
林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了Java中ConcurrentHashMap 的源码 ConcurrentH ...
- Java并发编程(十六):CyclicBarrier源码分析
前言 CyclicBarrier可以建立一个屏障,这个屏障可以阻塞一个线程直到指定的所有线程都达到屏障.就像团队聚餐,等所有人都到齐了再一起动筷子.根据Cyclic就可以发现CyclicBarri ...
- 【Java并发编程】16、ReentrantReadWriteLock源码分析
一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...
- java并发编程基础-ReentrantLock及LinkedBlockingQueue源码分析
ReentrantLock是一个较为常用的锁对象.在上次分析的uil开源项目中也多次被用到,下面谈谈其概念和基本使用. 概念 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 相 ...
- c++ 线程池_JAVA并发编程:线程池ThreadPoolExecutor源码分析
前面的文章已经详细分析了线程池的工作原理及其基本应用,接下来本文将从底层源码分析一下线程池的执行过程.在看源码的时候,首先带着以下两个问题去仔细阅读.一是线程池如何保证核心线程数不会被销毁,空闲线程数 ...
- 多线程与高并发(八):ThreadPoolExecutor源码解析, SingleThreadPool,CachedPool,FixedThreadPool,ForkJoinPoll 等
线程池 今天我们来看看JDK给我们提供的默认的线程池的实现. ThreadPoolExecutor:我们通常所说的线程池.多个线程共享同一个任务队列. SingleThreadPool CachedP ...
- Java 线程池ThreadPoolExecutor的应用与源码解析
ThreadPoolExecutor 工作原理 假设corePool=5,队列大小为100,maxnumPoolSize为10 向线程池新提交一个任务,会根据ThreadFactory创建一个新的线程 ...
最新文章
- 基于pytorch的模型稀疏训练与模型剪枝示例
- 斯坦福大学Christopher Manning:Transformer语言模型为什么能取得突破
- final修饰的变量是引用不能改变还是引用的对象不能改变
- 用户 ‘IIS APPPOOL\IdealTest‘ 登录失败解决方案
- 通过进程ID得到进程名
- 【Python】青少年蓝桥杯_每日一题_3.11_体重指数
- MapReduce的构思和框架结构
- 一次http完整的请求tcp报文分析
- Docker(二)-在Docker中部署Nginx实现负载均衡(视频)
- jooq代码生成_将jOOQ与Spring结合使用:代码生成
- (转)mysql基础命令
- [费用流]Bzoj P1877 晨跑
- nginx配置SSL实现服务器/客户端双向认证
- linux tcp文件分包_Linux内核参数优化
- Django项目:CRM(客户关系管理系统)--41--33PerfectCRM实现King_admin编辑整张表限制
- HTTP方法的幂等性
- 网易老司机花式刷屏,腾讯爸爸欲教其做人,最终结局...
- 俞军:百度首席产品架构师
- 计算机待机时间长黑屏怎么办,电脑黑屏?如何解决?
- 信用卡有很多好处,远不止解决你燃眉之急这么简单
热门文章
- 包含几通道数据_温度采集,无处不测!「数据采集」
- 生成树协议(STP)原理与配置PVST+实现负载均衡
- 我就是那个一直拿着死工资的人
- D3.js以及通用JS(JavaScript)读取并解析server端JSON的注意事项
- 使用IDEA+MVN 编译Spark 1.5.2 without hive
- hadoop2.2单节点集群的搭建
- vue学习笔记(五):对于vuex的理解 + 简单实例
- C#水晶报表,窗体不显示,闪退
- Python文件练习
- 从 ReactiveCocoa 中能学到什么?不用此库也能学以致用