深入理解Semaphore

使用

Semaphore是计数信号量。Semaphore管理一系列许可证。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。 
Semaphore经常用于限制获取某种资源的线程数量。下面举个例子,比如说操场上有5个跑道,一个跑道一次只能有一个学生在上面跑步,一旦所有跑道在使用,那么后面的学生就需要等待,直到有一个学生不跑了,下面是这个例子:

/*** 操场,有5个跑道* Created by Xingfeng on 2016-12-09.*/
public class Playground {/*** 跑道类*/static class Track {private int num;public Track(int num) {this.num = num;}@Overridepublic String toString() {return "Track{" +"num=" + num +'}';}}private Track[] tracks = {new Track(1), new Track(2), new Track(3), new Track(4), new Track(5)};private volatile boolean[] used = new boolean[5];private Semaphore semaphore = new Semaphore(5, true);/*** 获取一个跑道*/public Track getTrack() throws InterruptedException {semaphore.acquire(1);return getNextAvailableTrack();}/*** 返回一个跑道** @param track*/public void releaseTrack(Track track) {if (makeAsUsed(track))semaphore.release(1);}/*** 遍历,找到一个没人用的跑道** @return*/private Track getNextAvailableTrack() {for (int i = 0; i < used.length; i++) {if (!used[i]) {used[i] = true;return tracks[i];}}return null;}/*** 返回一个跑道** @param track*/private boolean makeAsUsed(Track track) {for (int i = 0; i < used.length; i++) {if (tracks[i] == track) {if (used[i]) {used[i] = false;return true;} else {return false;}}}return false;}}
  • 从上面可以看到,创建了5个跑道对象,并使用一个boolean类型的数组记录每个跑道是否被使用了,初始化了5个许可证的Semaphore,在获取跑道时首先调用acquire(1)获取一个许可证,在归还一个跑道是调用release(1)释放一个许可证。接下来再看启动程序,如下:
public class SemaphoreDemo {static class Student implements Runnable {private int num;private Playground playground;public Student(int num, Playground playground) {this.num = num;this.playground = playground;}@Overridepublic void run() {try {//获取跑道Playground.Track track = playground.getTrack();if (track != null) {System.out.println("学生" + num + "在" + track.toString() + "上跑步");TimeUnit.SECONDS.sleep(2);System.out.println("学生" + num + "释放" + track.toString());//释放跑道playground.releaseTrack(track);}} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {Executor executor = Executors.newCachedThreadPool();Playground playground = new Playground();for (int i = 0; i < 100; i++) {executor.execute(new Student(i+1,playground));}}}
  • 上面的代码中,Student类代表学生,首先获取跑道,一旦获取到就打印一句话,然后睡眠2s,然后再打印释放,最后归还跑道。

源码解析

Semaphore有两种模式,公平模式和非公平模式。公平模式就是调用acquire的顺序就是获取许可证的顺序,遵循FIFO;而非公平模式是抢占式的,也就是有可能一个新的获取线程恰好在一个许可证释放时得到了这个许可证,而前面还有等待的线程。

构造方法

Semaphore有两个构造方法,如下:

       public Semaphore(int permits) {sync = new NonfairSync(permits);}public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);}
  • 从上面可以看到两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。 

Semaphore内部基于AQS的共享模式,所以实现都委托给了Sync类。 
这里就看一下NonfairSync的构造方法:

 NonfairSync(int permits) {super(permits);}
  • 可以看到直接调用了父类的构造方法,Sync的构造方法如下:
Sync(int permits) {setState(permits);}
  • 可以看到调用了setState方法,也就是说AQS中的资源就是许可证的数量。

获取许可

先从获取一个许可看起,并且先看非公平模式下的实现。首先看acquire方法,acquire方法有几个重载,但主要是下面这个方法

public void acquire(int permits) throws InterruptedException {if (permits < 0) throw new IllegalArgumentException();sync.acquireSharedInterruptibly(permits);}
  • 从上面可以看到,调用了Sync的acquireSharedInterruptibly方法,该方法在父类AQS中,如下:
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {//如果线程被中断了,抛出异常if (Thread.interrupted())throw new InterruptedException();//获取许可失败,将线程加入到等待队列中if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}
  • AQS子类如果要使用共享模式的话,需要实现tryAcquireShared方法,下面看NonfairSync的该方法实现:
 protected int tryAcquireShared(int acquires) {return nonfairTryAcquireShared(acquires);}
  • 该方法调用了父类中的nonfairTyAcquireShared方法,如下:
final int nonfairTryAcquireShared(int acquires) {for (;;) {//获取剩余许可数量int available = getState();//计算给完这次许可数量后的个数int remaining = available - acquires;//如果许可不够或者可以将许可数量重置的话,返回if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}
  • 从上面可以看到,只有在许可不够时返回值才会小于0,其余返回的都是剩余许可数量,这也就解释了,一旦许可不够,后面的线程将会阻塞。看完了非公平的获取,再看下公平的获取,代码如下:
 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与NonFairSync的区别就在于会首先判断当前队列中有没有线程在等待,如果有,就老老实实进入到等待队列;而不像NonfairSync一样首先试一把,说不定就恰好获得了一个许可,这样就可以插队了。 

看完了获取许可后,再看一下释放许可。

释放许可

释放许可也有几个重载方法,但都会调用下面这个带参数的方法,

public void release(int permits) {if (permits < 0) throw new IllegalArgumentException();sync.releaseShared(permits);}
  • releaseShared方法在AQS中,如下:
public final boolean releaseShared(int arg) {//如果改变许可数量成功if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}
  • AQS子类实现共享模式的类需要实现tryReleaseShared类来判断是否释放成功,实现如下:
protected final boolean tryReleaseShared(int releases) {for (;;) {//获取当前许可数量int current = getState();//计算回收后的数量int next = current + releases;if (next < current) // overflowthrow new Error("Maximum permit count exceeded");//CAS改变许可数量成功,返回trueif (compareAndSetState(current, next))return true;}}
  • 从上面可以看到,一旦CAS改变许可数量成功,那么就会调用doReleaseShared()方法释放阻塞的线程。

减小许可数量

Semaphore还有减小许可数量的方法,该方法可以用于用于当资源用完不能再用时,这时就可以减小许可证。代码如下:

protected void reducePermits(int reduction) {if (reduction < 0) throw new IllegalArgumentException();sync.reducePermits(reduction);}
  • 可以看到,委托给了Sync,Sync的reducePermits方法如下:
  final void reducePermits(int reductions) {for (;;) {//得到当前剩余许可数量int current = getState();//得到减完之后的许可数量int next = current - reductions;if (next > current) // underflowthrow new Error("Permit count underflow");//如果CAS改变成功if (compareAndSetState(current, next))return;}}
  • 从上面可以看到,就是CAS改变AQS中的state变量,因为该变量代表许可证的数量。

获取剩余许可数量  

Semaphore还可以一次将剩余的许可数量全部取走,该方法是drain方法,如下:

public int drainPermits() {return sync.drainPermits();}
  • Sync的实现如下:
 final int drainPermits() {for (;;) {int current = getState();if (current == 0 || compareAndSetState(current, 0))return current;}}
  • 可以看到,就是CAS将许可数量置为0。

总结

Semaphore是信号量,用于管理一组资源。其内部是基于AQS的共享模式,AQS的状态表示许可证的数量,在许可证数量不够时,线程将会被挂起;而一旦有一个线程释放一个资源,那么就有可能重新唤醒等待队列中的线程继续执行。

转自:https://blog.csdn.net/qq_19431333/article/details/70212663

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

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

相关文章

【算法系列之四】柱状图储水

题目&#xff1a; 给定一个数组&#xff0c;每个位置的值代表一个高度&#xff0c;那么整个数组可以看做是一个直方图&#xff0c; 如果把这个直方图当作容器的话&#xff0c;求这个容器能装多少水 例如&#xff1a;3&#xff0c;1&#xff0c;2&#xff0c;4 代表第一个位…

盐城大数据产业园人才公寓_岳西大数据产业园规划设计暨建筑设计方案公布,抢先一睹效果图...

近日&#xff0c;岳西县大数据产业园规划设计暨建筑设计方案公布。岳西县大数据产业园项目总占地面积17014.10㎡(约合25.52亩)&#xff0c;拟建总建筑面积约为61590.84㎡(地上建筑面积39907.49㎡&#xff0c;地下建筑面积21602.35㎡)。以“科技圆环”为主题&#xff0c;组建出一…

【算法系列之五】对称二叉树

给定一个二叉树&#xff0c;检查它是否是镜像对称的。 例如&#xff0c;二叉树 [1,2,2,3,4,4,3] 是对称的。 1/ \2 2/ \ / \ 3 4 4 3但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 1/ \2 2\ \3 3 说明: 如果你可以运用递归和迭代两种方法解决这个问题&a…

【算法系列之六】两整数之和

不使用运算符 和 - &#xff0c;计算两整数 a 、b 之和。 示例 1: 输入: a 1, b 2 输出: 3示例 2: 输入: a -2, b 3 输出: 1 方法一&#xff1a;递归 public static int getSum1(int a, int b) {if ((a & b) ! 0) { // 判断是否有进位return getSum1(a ^ b, (a &…

cuda默认函数与c++冲突_好程序员Python教程系列-第8讲:函数和模块

好程序员Python教程系列-第8讲&#xff1a;函数和模块&#xff0c;在讲解本章节的内容之前&#xff0c;我们先来研究一道数学题&#xff0c;请说出下面的方程有多少组正整数解。事实上&#xff0c;上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案&#xff0c;所…

【算法系列之七】合并两个有序链表

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例&#xff1a; 输入&#xff1a;1->2->4, 1->3->4 输出&#xff1a;1->1->2->3->4->4/*** Definition for singly-linked list.* public cla…

mfc让图片与按钮一起_对许多张图片进行批量裁剪,看看我是如何快速做到的

概要&#xff1a;当我们需要对很多图片进行批量裁剪时&#xff0c;以往的办法是自己一张一张图片去操作&#xff0c;非常麻烦。有没有这样一个工具&#xff0c;能够帮我们批量进行处理呢&#xff1f;之前小编在网上找了非常多的软件&#xff0c;一个一个地安装试用&#xff0c;…

【算法系列之八】删除链表的倒数第N个节点

给定一个链表&#xff0c;删除链表的倒数第 n 个节点&#xff0c;并且返回链表的头结点。 示例&#xff1a; 给定一个链表: 1->2->3->4->5, 和 n 2.当删除了倒数第二个节点后&#xff0c;链表变为 1->2->3->5.说明&#xff1a; 给定的 n 保证是有效的…

手写分页sql_分页查询SQL语句

表结构&#xff1a;DROP TABLE IF EXISTS zhoufoxcn.userlist;CREATE TABLE zhoufoxcn.userlist (UserId int(10) unsigned NOT NULL auto_increment,UserName varchar(45) NOT NULL,Age int(10) unsigned NOT NULL default 10,Sex tinyint(3) unsigned NOT NULL default 1,Ta…

【算法系列之九】合并两个有序数组

给定两个有序整数数组 nums1 和 nums2&#xff0c;将 nums2 合并到 nums1 中&#xff0c;使得 num1 成为一个有序数组。 说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。你可以假设 nums1 有足够的空间&#xff08;空间大小大于或等于 m n&#xff09;来保存 nums2 …

把网卡指定给vm虚拟机_为VMWare虚拟网卡指定静态的MAC地址

当你把虚拟机移到另一台主机或在同一台主机但不同的路径时&#xff0c;虚拟机的MAC地址将会更改。默认情况下VMWare会保证MAC地址的唯一性却不保存固定性&#xff0c;在每次开启虚拟机里的系统时都可能重新分配MAC地址来保证唯一性&#xff0c;若你想保证即使虚拟机被移动后&am…

【算法系列之十】三数之和

给定一个包含 n 个整数的数组 nums&#xff0c;判断 nums 中是否存在三个元素 a&#xff0c;b&#xff0c;c &#xff0c;使得 a b c 0 &#xff1f;找出所有满足条件且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 例如, 给定数组 nums [-1, 0, 1,…

android 动态获取全县_省市县 ------ 三级滚动(android)

预先加载仿滚轮实现的全部数据mCityPickerView.init(this);③ 点击响应&#xff1a;ss.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {CityConfig cityConfig new CityConfig.Builder().title("选择城市")//标题.build();m…

发电厂电气部分第三版pdf_火力发电厂电气主接线的特点

根据火力发电厂的容量及其在电力系统中的地位&#xff0c;一般可将火力发电厂分为区域性火力发电厂和地方性火力发电厂。这两类火力发电厂的电气主接线有各自的特点。一、区域性火力发电厂的电气主接线1、单机容量及总装机容量都较大单机容量多为300MW、600MW和少量1000MW,电厂…

【算法系列之十一】k个一组翻转链表

给出一个链表&#xff0c;每 k 个节点一组进行翻转&#xff0c;并返回翻转后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么将最后剩余节点保持原有顺序。 示例 : 给定这个链表&#xff1a;1->2->3-&g…

ghostblog主题_读Ghost博客源码与自定义Ghost博客主题

我使用的Ghost博客一直使用者默认的Casper主题。我向来没怎么打理过自己博客&#xff0c;一方面认为自己不够专业&#xff0c;很难写出质量比较高的文字&#xff1b;另一方面认为博客太耗时间&#xff0c;很容易影响正常的工作内容。最近公司即将搬迁&#xff0c;我的开发工作也…

【算法系列之十二】最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数&#xff0c;使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。 例如&#xff0c;给定数组 nums [-1&#xff0c;2&#xff0c;1&#xff0c;-4], 和 target 1…

定义一个dto对象_业务代码的救星——Java 对象转换框架 MapStruct 妙用

在业务项目的开发中&#xff0c;我们经常需要将 Java 对象进行转换&#xff0c;比如从将外部微服务得到的对象转换为本域的业务对象 domainobject&#xff0c;将 domainobject 转为数据持久层的 dataobject&#xff0c;将 domainobject 转换为 DTO 以便返回给外部调用方等。在转…

JVM调优总结 -Xms -Xmx -Xmn -Xss

堆大小设置 JVM 中最大堆大小有三方面限制&#xff1a;相关操作系统的数据模型&#xff08;32-bt还是64-bit&#xff09;限制&#xff1b;系统的可用虚拟内存限制&#xff1b;系统的可用物理内存限制。32位系统 下&#xff0c;一般限制在1.5G~2G&#xff1b;64为操作系统对内存…

discuz设置用户每天回帖数_[建站教程]Discuz3.4设置QQ互联登陆教程

虽然现在很多人已经不在使用QQ了&#xff0c;但瘦死的骆驼比马大&#xff0c;QQ的用户基数还是很大&#xff0c;而且QQ里有大量的年轻用户&#xff0c;像我的表妹&#xff0c;表弟刚上初中。他们是忠诚的QQ用户。为了获取这批年轻的用户&#xff0c;我们还是有必要让网站支持QQ…