Java并发编程笔记之Semaphore信号量源码分析

JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那么,Semaphore 的内部实现是怎样的呢?

  Semaphore 信号量也是Java 中一个同步容器,与CountDownLatch 和 CyclicBarrier 不同之处在于它内部的计数器是递增的。为了能够一览Semaphore的内部结构,我们首先要看一下Semaphore的类图,类图,如下所示:

 

 如上类图可以知道Semaphoren内部还是使用AQS来实现的,Sync只是对AQS的一个修饰,并且Sync有两个实现类,分别代表获取信号量的时候是否采取公平策略。创建Semaphore的时候会有一个变量标示是否使用公平策略,源码如下:

    public Semaphore(int permits) {sync = new NonfairSync(permits);}public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new       NonfairSync(permits);}Sync(int permits) {setState(permits);}

如上面代码所示,Semaphore默认使用的是非公平策略,如果你需要公平策略,则可以使用带两个参数的构造函数来构造Semaphore对象,另外和CountDownLatch一样,构造函数里面传递的初始化信号量个数 permits 被赋值给了AQS 的state状态变量,也就是说这里AQS的state值表示当前持有的信号量个数。

 

接下来我们主要看看Semaphore实现的主要方法的源码,如下:

  1.void acquire() 当前线程调用该方法的时候,目的是希望获取一个信号量资源,如果当前信号量计数个数大于 0 ,并且当前线程获取到了一个信号量则该方法直接返回,当前信号量的计数会减少 1 。否则会被放入AQS的阻塞队列,当前线程被挂起,直到其他线程调用了release方法释放了信号量,并且当前线程通过竞争获取到了改信号量。当前线程被其他线程调用了 interrupte()方法中断后,当前线程会抛出 InterruptedException异常返回。源码如下:

   public void acquire() throws InterruptedException {//传递参数为1,说明要获取1个信号量资源sync.acquireSharedInterruptibly(1);}public final void acquireSharedInterruptibly(int arg)throws InterruptedException {//(1)如果线程被中断,则抛出中断异常if (Thread.interrupted())throw new InterruptedException();//(2)否者调用sync子类方法尝试获取,这里根据构造函数确定使用公平策略if (tryAcquireShared(arg) < 0)//如果获取失败则放入阻塞队列,然后再次尝试如果失败则调用park方法挂起当前线程
        doAcquireSharedInterruptibly(arg);}

如上代码可知,acquire()内部调用了sync的acquireSharedInterruptibly  方法,后者是对中断响应的(如果当前线程被中断,则抛出中断异常),尝试获取信号量资源的AQS的方法tryAcquireShared 是由 sync 的子类实现,所以这里就要分公平性了,这里先讨论非公平策略 NonfairSync 类的 tryAcquireShared 方法,源码如下:

protected int tryAcquireShared(int acquires) {return nonfairTryAcquireShared(acquires);
}final
int nonfairTryAcquireShared(int acquires) {for (;;) {//获取当前信号量值int available = getState();//计算当前剩余值int remaining = available - acquires;//如果当前剩余小于0或者CAS设置成功则返回if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;} }

如上代码,先计算当前信号量值(available)减去需要获取的值(acquires) 得到剩余的信号量个数(remaining),如果剩余值小于 0 说明当前信号量个数满足不了需求,则直接返回负数,然后当前线程会被放入AQS的阻塞队列,当前线程被挂起。如果剩余值大于 0 则使用CAS操作设置当前信号量值为剩余值,然后返回剩余值。另外可以知道NonFairSync是非公平性获取的,是说先调用aquire方法获取信号量的线程不一定比后来者先获取锁。

 

接下来我们要看看公平性的FairSync 类是如何保证公平性的,源码如下:

 protected int tryAcquireShared(int acquires) {for (;;) {if (hasQueuedPredecessors())return -1;int available = getState();int remaining = available - acquires;if (remaining < 0 || compareAndSetState(available, remaining))return remaining;}}

可以知道公平性还是靠 hasQueuedPredecessors 这个方法来做的,以前的随笔已经讲过公平性是看当前线程节点是否有前驱节点也在等待获取该资源,如果是则自己放弃获取的权力,然后当前线程会被放入AQS阻塞队列,否则就去获取。hasQueuedPredecessors源码如下:

public final boolean hasQueuedPredecessors() {Node t = tail; Node h = head;Node s;return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}

如上面代码所示,如果当前线程节点有前驱节点则返回true,否则如果当前AQS队列为空 或者 当前线程节点是AQS的第一个节点则返回 false ,其中,如果 h == t 则说明当前队列为空则直接返回 false,如果 h !=t 并且 s == null 说明有一个元素将要作为AQS的第一个节点入队列(回顾下 enq 函数第一个元素入队列是两步操作,首先创建一个哨兵头节点,然后第一个元素插入到哨兵节点后面),那么返回 true,如果  h !=t 并且 s != null 并且  s.thread != Thread.currentThread() 则说明队列里面的第一个元素不是当前线程则返回 true。

 

  2.void acquire(int permits) 该方法与 acquire() 不同在与后者只需要获取一个信号量值,而前者则获取指定 permits 个,源码如下:

public void acquire(int permits) throws InterruptedException {if (permits < 0) 
       throw new IllegalArgumentException();sync.acquireSharedInterruptibly(permits); }

 

  3.void acquireUninterruptibly() 该方法与 acquire() 类似,不同之处在于该方法对中断不响应,也就是当当前线程调用了 acquireUninterruptibly 获取资源过程中(包含被阻塞后)其它线程调用了当前线程的 interrupt()方法设置了当前线程的中断标志当前线程并不会抛出 InterruptedException 异常而返回。源码如下:

public void acquireUninterruptibly() {sync.acquireShared(1);
}

 

  4.void acquireUninterruptibly(int permits) 该方法与 acquire(int permits) 不同在于该方法对中断不响应。源码如如下:

 public void acquireUninterruptibly(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.acquireShared(permits);}

 

  5.void release() 该方法作用是把当前 semaphore对象的信号量值增加 1 ,如果当前有线程因为调用 acquire 方法被阻塞放入了 AQS的阻塞队列,则会根据公平策略选择一个线程进行激活,激活的线程会尝试获取刚增加的信号量,源码如下:

  public void release() {//(1)arg=1sync.releaseShared(1);}public final boolean releaseShared(int arg) {//(2)尝试释放资源if (tryReleaseShared(arg)) {//(3)资源释放成功则调用park唤醒AQS队列里面最先挂起的线程
            doReleaseShared();return true;}return false;}protected final boolean tryReleaseShared(int releases) {for (;;) {//(4)获取当前信号量值int current = getState();//(5)当前信号量值增加releases,这里为增加1int next = current + releases;if (next < current) // 移除处理throw new Error("Maximum permit count exceeded");//(6)使用cas保证更新信号量值的原子性if (compareAndSetState(current, next))return true;}}

如上面代码可以看到 release()方法中对 sync.releaseShared(1),可以知道release方法每次只会对信号量值增加 1 ,tryReleaseShared方法是无限循环,使用CAS保证了 release 方法对信号量递增 1 的原子性操作。当tryReleaseShared 方法增加信号量成功后会执行代码(3),调用AQS的方法来激活因为调用acquire方法而被阻塞的线程。

 

  6.void release(int permits) 该方法与不带参数的不同之处在于前者每次调用会在信号量值原来基础上增加 permits,而后者每次增加 1。源码如下:

public void release(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.releaseShared(permits);
}

另外注意到这里调用的是 sync.releaseShared 是共享方法,这说明该信号量是线程共享的,信号量没有和固定线程绑定,多个线程可以同时使用CAS去更新信号量的值而不会阻塞。

 

到目前已经知道了其原理,接下来用一个例子来加深对Semaphore的理解,例子如下:

package com.hjc;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;/*** Created by cong on 2018/7/8.*/
public class SemaphoreTest {// 创建一个Semaphore实例private static volatile Semaphore semaphore = new Semaphore(0);public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(2);// 加入线程A到线程池executorService.submit(new Runnable() {public void run() {try {System.out.println(Thread.currentThread() +  " over");semaphore.release();} catch (Exception e) {e.printStackTrace();}}});// 加入线程B到线程池executorService.submit(new Runnable() {public void run() {try {System.out.println(Thread.currentThread() +  " over");semaphore.release();} catch (Exception e) {e.printStackTrace();}}});// 等待子线程执行完毕,返回semaphore.acquire(2);System.out.println("all child thread over!");//关闭线程池
        executorService.shutdown();}
}

运行结果如下:

类似于 CountDownLatch,上面我们的例子也是在主线程中开启两个子线程进行执行,等所有子线程执行完毕后主线程在继续向下运行。

如上代码首先首先创建了一个信号量实例,构造函数的入参为 0,说明当前信号量计数器为 0,然后 main 函数添加两个线程任务到线程池,每个线程内部调用了信号量的 release 方法,相当于计数值递增一,最后在 main 线程里面调用信号量的 acquire 方法,参数传递为 2 说明调用 acquire 方法的线程会一直阻塞,直到信号量的计数变为 2 时才会返回。

看到这里也就明白了,如果构造 Semaphore 时候传递的参数为 N,在 M 个线程中调用了该信号量的 release 方法,那么在调用 acquire 对 M 个线程进行同步时候传递的参数应该是 M+N;

 

对CountDownLatch,CyclicBarrier,Semaphored这三者之间的比较总结:

  1.CountDownLatch 通过计数器提供了更灵活的控制,只要检测到计数器为 0,而不管当前线程是否结束调用 await 的线程就可以往下执行,相比使用 jion 必须等待线程执行完毕后主线程才会继续向下运行更灵活。

  2.CyclicBarrier 也可以达到 CountDownLatch 的效果,但是后者当计数器变为 0 后,就不能在被复用,而前者则使用 reset 方法可以重置后复用,前者对同一个算法但是输入参数不同的类似场景下比较适用。

  3.而 semaphore 采用了信号量递增的策略,一开始并不需要关心需要同步的线程个数,等调用 aquire 时候在指定需要同步个数,并且提供了获取信号量的公平性策略。

转载于:https://www.cnblogs.com/huangjuncong/p/9280646.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/427496.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

三款常用IP发包工具介绍

AntPower 版权所有© 2003 技术文章http://www.antpower.org 第1 页共14 页AntPower&#xff0d;技术文章三款常用IP 发包工具介绍小蚁雄心成员郎国军著lgjqingdao.cngb.comURL修订版本版本时间修订人说明AntPower 版权所有© 2003 技术文章http://www.antpower.org 第…

python中读取指定的行和列_Python怎么获取excle中指定行和列的值?

https://www.cnblogs.com/xiazhenyu/ *** 学而思之、思而记之、记而习之 ***f"dict_file.txt" #定义文件名 def writefile(key,value): with open(f, "w") as file: # 只需要将之前的”w"改为“a"即可&#xff0c;代表追加内容&#xff0c;“w”…

设计模式--命令模式

实验16&#xff1a;命令模式 本次实验属于模仿型实验&#xff0c;通过本次实验学生将掌握以下内容&#xff1a; 1、理解命令模式的动机&#xff0c;掌握该模式的结构&#xff1b; 2、能够利用命令模式解决实际问题。 [实验任务]&#xff1a;多次撤销和重复的命令模式 某系…

进入显示器工厂模式的方法 【95种品牌 维修珍藏资料】

常见CRT显示器的工厂模式进入方法 谁家的电视机坏了&#xff0c;图像的颜色乱了&#xff0c;肯定要请电视机维修人员来修。但是大家也许听说过&#xff0c;有时候维修人员到了家里&#xff0c;连螺丝刀都不拿&#xff0c;只是把遥控器“乱”按一通&#xff0c;电视机的故障就…

SecureCRT SSH 语法高亮

主要原因1.term类型不对,不支持彩色.在secureCRT上设置Options->SessionOptions ->Emulation,然后把Terminal类型改成xterm&#xff0c;并点中ANSI Color复选框。然后ls看看,发现文件名和目录已经是彩色了.但是可能vi打开某些文件依然不是彩色按:进入命令模式输入syntax …

跨站脚本专题 XSS

再分享一下我老师大神的人工智能教程吧。零基础&#xff01;通俗易懂&#xff01;风趣幽默&#xff01;还带黄段子&#xff01;希望你也加入到我们人工智能的队伍中来&#xff01;https://blog.csdn.net/jiangjunshow

wxpython界面切换_Python图形界面开发—wxPython库的布局管理及页面切换

前言 wxPython是基于Python的跨平台GUI扩展库&#xff0c;对wxWidgets&#xff08; C 编写&#xff09;封装实现。GUI程序的开发中界面布局是很重要的一个部分&#xff0c;合理的页面布局能够给予用户良好使用体验。虽然在GUI的控件和窗口布局上可以使用坐标&#xff0c;但更多…

javah找不到类文件

这样即可&#xff0c;在src目录下寻找类&#xff0c;类要写全&#xff0c;即包名.类名 转载于:https://www.cnblogs.com/Java-Starter/p/9283830.html

水木周平戏说中国网络黑幽默

1&#xff1a; 百度贴吧都知道吧&#xff0c;有狗吧&#xff0c;牛吧&#xff0c;十一生肖都有。&#xff08;没错&#xff01;只有十一生肖&#xff01;&#xff09;唯独没有鸡吧。我属鸡&#xff0c;为此我很伤心。----听说过草木皆兵吗&#xff1f; 2: 这个世界上最远的距离…

python卸载opencv_20.Windows python,opencv的安装与卸载

本机Windows7系统&#xff0c;之前安装了Python3.5的版本&#xff0c;后来因为要装opencv&#xff0c;而opencv只支持Python2.7的版本&#xff0c;所以需要将Python3.5进行卸载。在控制面板-卸载程序&#xff0c;将Python卸载后删除其注册表等残留文件 下载Python2.7&#xff0…

cookie和session 以及Django中应用

cookie和session 以及Django中应用 cookie和session机制 cookie和session机制 cookie机制采用的是在客户端保持状态的方案。作用就是为了解决HTTP协议无状态的缺陷所做的努力。 session机制采用的是一种在客户端与服务器之间保持状态的解决方案。由于采用服务器端保持状态的方案…

LinuX 硬盘分区细节详谈 【 整理至 LinuxSir BY FreeXploiT 】

系统引导过程及硬盘分区结构论述作者&#xff1a; zhy2111314来自&#xff1a; LinuxSir.Org ouc.edu.cn摘要&#xff1a; 本文是理论性文档&#xff0c;主要讲述系统引导过程以及硬盘的物理结构&#xff1b;正文一、系统引导过程简介系统引导过程主要由以下几个步骤组成(以硬盘…

GCD与LCM【数论】

题目大意&#xff1a; 给出两个数的GCDGCD和LCMLCM&#xff0c;求这两个数的最小差值。 IuputIuput 6 36 OutputOutput 6 思路&#xff1a; 一道数论题。 我们设这两个数分别为xx和yy且x≤yx≤y&#xff0c;ggcd(x,y)ggcd(x,y)&#xff0c;llcm(x,y)llcm(x,y)&#xff0c;那…

破解WEP密钥过程全解(上)

WLAN技术出现之后&#xff0c;“安全”就成为始终伴随在“无线”这个词身边的影子&#xff0c;针对无线网络技术中涉及的安全认证加密协议的攻击与破解就层出不穷。现在&#xff0c;因特网上可能有数以百计&#xff0c;甚至以千计的文章介绍关于怎么攻击与破解WEP&#xff0c;但…

bfc是什么_一次弄懂css的BFC

前言BFC在css的学习中是重要的但不易理解的概念&#xff0c;BFC也牵扯了很多其他问题&#xff0c;如浮动、定位、盒模型等&#xff0c;因此弄懂BFC是很有必要的。本文对BFC进行总结&#xff0c;希望对你有所帮助。BFC是什么&#xff1f;先看看MDN的定义&#xff1a;块格式化上下…

ES6模块的import和export用法总结

ES6之前已经出现了js模块加载的方案&#xff0c;最主要的是CommonJS和AMD规范。commonjs主要应用于服务器&#xff0c;实现同步加载&#xff0c;如nodejs。AMD规范应用于浏览器&#xff0c;如requirejs&#xff0c;为异步加载。同时还有CMD规范&#xff0c;为同步加载方案如sea…

windows网关详解 【了解网关的重要性,增加网络性能】【FreeXploiT综合文】

理解Windows中的路由表和默认网关 每一个Windows系统中都具有IP路由表&#xff0c;它存储了本地计算机可以到达的网络目的地址范围和如何到达的路由信息。路由表是TCP/IP通信的基础&#xff0c;本地计算机上的任何TCP/IP通信都受到路由表的控制。 理解路由表 你可以运行 route …

隐藏该监视器无法显示模式_【春星开讲 | 9137】达芬奇4K调色监看的好伙伴——明基PD2720U专业显示器...

近几年来&#xff0c;4K制作成为影视行业的一个热词&#xff0c;从标清到高清&#xff0c;从高清到超高清&#xff0c;从2K到4K&#xff0c;影视前期拍摄设备和后期制作流程都在发生着剧烈的变革。单就我所从事的影视调色行业而言&#xff0c;4K调色也是很多调色师梦寐以求但也…

基金基础知识

基金基础知识 一、基金的定义 基金&#xff08;Fund&#xff09;从广义上说&#xff0c;基金是指为了某种目的而设立的具有一定数量的资金。主要包括信托投资基金、公积金、保险基金、退休基金&#xff0c;各种基金会的基金。从会计角度透析&#xff0c;基金是一个狭义的概念&a…

了解WWW服务与HTTP协议 【入门与应用】

轻松认识HTTP协议的概念和工作原理 当我们想浏览一个网站的时候&#xff0c;只要在浏览器的地址栏里输入网站的地址就可以了&#xff0c;例如&#xff1a;www.microsoft.com&#xff0c;但是在浏览器的地址栏里面出现的却是&#xff1a;http://www.microsoft.com&#xff0c;你…