What

Semaphore标识信号量,允许指定数量的线程同时访问某个资源

How

通过以下两部实现信号量:

  • acquire方法用于获得准入许可(如果没有获得许可,则进行等待,直到有线程释放许可而获得许可为止)
  • release用于释放准入许可

应用场景

  1. 实现某种资源池限制,类似于数据库连接池
  2. 对容器施加边界,比如一个集合中最多只能添加5个元素
  3. 资源并发访问数量限制
  4. 当作普通的使用(信号量为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源码解析

关键方法如下:

  1. 构造方法:new Semaphore(5);
  2. 获取许可:semaphore.acquire();
  3. 释放许可: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()判断,其余一样。

在方法中可以看出,最终返回的是剩余的许可数量,有如下几种情况:

  1. 如果剩余许可数量<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); // 获取失败 就取消获取}}}
    
  2. 否则就是拿到了许可数量,继续正常执行,不阻塞

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的应用与源码解析相关推荐

  1. Java 并发编程CyclicBarrier的应用与源码解析(基于ReentrantLock实现)

    什么是CyclicBarrier? CyclicBarrie和上一篇中讲到CountDownLatch很类似,它能阻塞一组线程直到某个事件的发生. 栅栏与闭锁的关键区别在于:所有必须同时到达栅栏位置才 ...

  2. Java 并发编程CountDownLatch的应用与源码解析

    应用场景 CountDownLatch是一个多线程控制工具.用来控制线程的等待. 设置需要countDown的数量,然后每一个线程执行完毕后调用countDown()方法,而在主线程中调用await( ...

  3. Java并发编程与技术内幕:ConcurrentHashMap源码解析

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文主要讲了Java中ConcurrentHashMap 的源码 ConcurrentH ...

  4. Java并发编程(十六):CyclicBarrier源码分析

    前言   CyclicBarrier可以建立一个屏障,这个屏障可以阻塞一个线程直到指定的所有线程都达到屏障.就像团队聚餐,等所有人都到齐了再一起动筷子.根据Cyclic就可以发现CyclicBarri ...

  5. 【Java并发编程】16、ReentrantReadWriteLock源码分析

    一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...

  6. java并发编程基础-ReentrantLock及LinkedBlockingQueue源码分析

    ReentrantLock是一个较为常用的锁对象.在上次分析的uil开源项目中也多次被用到,下面谈谈其概念和基本使用. 概念 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 相 ...

  7. c++ 线程池_JAVA并发编程:线程池ThreadPoolExecutor源码分析

    前面的文章已经详细分析了线程池的工作原理及其基本应用,接下来本文将从底层源码分析一下线程池的执行过程.在看源码的时候,首先带着以下两个问题去仔细阅读.一是线程池如何保证核心线程数不会被销毁,空闲线程数 ...

  8. 多线程与高并发(八):ThreadPoolExecutor源码解析, SingleThreadPool,CachedPool,FixedThreadPool,ForkJoinPoll 等

    线程池 今天我们来看看JDK给我们提供的默认的线程池的实现. ThreadPoolExecutor:我们通常所说的线程池.多个线程共享同一个任务队列. SingleThreadPool CachedP ...

  9. Java 线程池ThreadPoolExecutor的应用与源码解析

    ThreadPoolExecutor 工作原理 假设corePool=5,队列大小为100,maxnumPoolSize为10 向线程池新提交一个任务,会根据ThreadFactory创建一个新的线程 ...

最新文章

  1. 基于pytorch的模型稀疏训练与模型剪枝示例
  2. 斯坦福大学Christopher Manning:Transformer语言模型为什么能取得突破
  3. final修饰的变量是引用不能改变还是引用的对象不能改变
  4. 用户 ‘IIS APPPOOL\IdealTest‘ 登录失败解决方案
  5. 通过进程ID得到进程名
  6. 【Python】青少年蓝桥杯_每日一题_3.11_体重指数
  7. MapReduce的构思和框架结构
  8. 一次http完整的请求tcp报文分析
  9. Docker(二)-在Docker中部署Nginx实现负载均衡(视频)
  10. jooq代码生成_将jOOQ与Spring结合使用:代码生成
  11. (转)mysql基础命令
  12. [费用流]Bzoj P1877 晨跑
  13. nginx配置SSL实现服务器/客户端双向认证
  14. linux tcp文件分包_Linux内核参数优化
  15. Django项目:CRM(客户关系管理系统)--41--33PerfectCRM实现King_admin编辑整张表限制
  16. HTTP方法的幂等性
  17. 网易老司机花式刷屏,腾讯爸爸欲教其做人,最终结局...
  18. 俞军:百度首席产品架构师
  19. 计算机待机时间长黑屏怎么办,电脑黑屏?如何解决?
  20. 信用卡有很多好处,远不止解决你燃眉之急这么简单

热门文章

  1. 包含几通道数据_温度采集,无处不测!「数据采集」
  2. 生成树协议(STP)原理与配置PVST+实现负载均衡
  3. 我就是那个一直拿着死工资的人
  4. D3.js以及通用JS(JavaScript)读取并解析server端JSON的注意事项
  5. 使用IDEA+MVN 编译Spark 1.5.2 without hive
  6. hadoop2.2单节点集群的搭建
  7. vue学习笔记(五):对于vuex的理解 + 简单实例
  8. C#水晶报表,窗体不显示,闪退
  9. Python文件练习
  10. 从 ReactiveCocoa 中能学到什么?不用此库也能学以致用