Java多线程并发控制工具信号量Semaphore,实现原理及案例

Wesley13
• 阅读 725

信号量(Semaphore)是Java多线程兵法中的一种JDK内置同步器,通过它可以实现多线程对公共资源的并发访问控制。一个线程在进入公共资源时需要先获取一个许可,如果获取不到许可则要等待其它线程释放许可,每个线程在离开公共资源时都会释放许可。其实可以将Semaphore看成一个计数器,当计数器的值小于许可最大值时,所有调用acquire方法的线程都可以得到一个许可从而往下执行。而调用release方法则可以让计数器的值减一。

信号量的主要应用场景是控制最多N个线程同时地访问资源,其中计数器的最大值即是许可的最大值N。以停车场为例,假设停车场一共有8个车位,其中6个车位已被停放,然后来了两辆汽车,此时因为刚好剩下两个车位所以这两辆车都能停放。接着又来了一辆车,现在已经没有空位了所以只能等待其它车离开。此时刚好一辆红色汽车离开停车场,来开后黄车刚好可以停进去,假如又有一辆汽车进来则该车又得等待。如此往复。这个过程中停车场就是公共资源,车位数就是信号量最大许可数,车辆就好比线程。

01

四要素

信号量的四要素为:最大许可数公平模式acquire方法以及release方法。最大许可数和公平模式在构建Semaphore对象时指定,分别表示公共资源最多可以多少个线程同时访问以及获取许可时是否使用公平模式。acquire方法用于获取许可,假如因为不足许可的话则进入等待状态。release方法用于释放许可。

02

非公平模式的实现

Semaphore类的实现是基于AQS同步器来实现的,不管是公平模式还是非公平模式都是基于AQS的共享模式,只是在获取许可的操作逻辑有差异。Semaphore的默认模式为非公平模式,我们先看非公平模式的实现。

Java多线程并发控制工具信号量Semaphore,实现原理及案例

Semaphore类的几个主要方法如下所示,其中提供了两个构造函数,相关的两个参数为许可最大数和是否使用公平模式,其中FairSync是公平模式的同步器而NonfairSync则是非公平模式的同步器。有两个acquire方法,无参时默认是一次获取1个许可,而如果传入整型参数则表示一次获取若干个许可。对应地,也有两个release方法,无参时表示释放1个许可,而整型参数则表示一次释放若干个许可。Semaphore主要的几个方法如下

Java多线程并发控制工具信号量Semaphore,实现原理及案例

Semaphore内部的Syn子类是公平模式FairSync类和非公平模式NonfairSync类的抽象父类,许可最大数与AQS同步器的状态变量对应。因为模式是非公平模式,所以这里提供了非公平的许可获取方法nonfairTryAcquireShared。非公平模式其实就是在许可数量允许的情况下,让所有线程都进行自旋操作,而不管它们先来后到的顺序,全部线程放到一起去竞争许可。其中compareAndSetState方法提供了CAS算法从而能够保证并发修改许可值,而剩余许可数等于当前可用许可值减去当前消耗许可数,需要注意的是当剩余许可数小于0时则返回负数从而导致线程会进入等待队列中。tryReleaseShared方法则提供了释放许可的操作,不管是不是公平模式都使用该方法即可,释放许可的逻辑是相同的。通过自旋操作来将释放的许可数增加到当前剩余许可数。

Java多线程并发控制工具信号量Semaphore,实现原理及案例

非公平模式NonfairSync类的实现主要是tryAcquireShared方法,直接调用父类Sync的的nonfairTryAcquireShared方法即可。

Java多线程并发控制工具信号量Semaphore,实现原理及案例

非公平同步器

03

公平模式实现

公平模式与非公平模式的主要差异就在获取许可时的机制,非公平模式直接通过自旋操作让所有线程竞争许可,从而导致了非公平。而公平模式则通过队列来实现公平机制。它们的差异就在tryAcquireShared方法,我们看公平模式的tryAcquireShared方法。实际上不同的地方就在下图中加了方框的两行代码,它会检查是否已经存在等待队列,如果已经有等待队列则返回-1,返回-1则表示让AQS同步器将当前线程进入等待队列中,队列则意味着公平。实际上,这也并非是严格的公平,在前面讲到的AQS同步器的公平性章节有深入讲过AQS的公平性,如果忘记了可以重新查阅加深理解。而且在为达到最大许可数的情况下,所有线程也并没有进入等待队列中,而是全部线程进行自旋获取许可。

Java多线程并发控制工具信号量Semaphore,实现原理及案例

公平模式的实现

04

 案例 1 

我们先看一个简单的例子,首先实例化一个拥有5个许可的信号量对象,然后一共有10个线程一同尝试获取5个许可,得到许可的线程将value进行累加1,接着睡眠五秒,最后释放许可。

Java多线程并发控制工具信号量Semaphore,实现原理及案例

以上程序输出如下,其中有五个线程输出“counting number : xx”后其他线程则开始等待。大概等待5秒后获得许可的五个线程执行释放许可操作,然后其它线程才能获得许可并往下执行。

Java多线程并发控制工具信号量Semaphore,实现原理及案例

05

 案例 2 

例子二与例子一很相似,不同的地方在于每次获取许可时会消耗2个许可,同样释放时也释放2个许可。这里实例化一个拥有6个许可的信号量对象,然后10个线程一同尝试获取许可。但这次最多只能同时3个线程得到许可,也就是三个线程得到许可后对value值进行累加1,然后睡眠5秒后释放许可。接着另外三个线程又获得许可往下执行,直到10个线程都执行完。

Java多线程并发控制工具信号量Semaphore,实现原理及案例

Java多线程并发控制工具信号量Semaphore,实现原理及案例

06

 总结 

本文介绍了一个JDK内置的同步器——信号量(Semaphore),通过它能够控制最多若干个线程访问公共资源。它可以看成是一个计数器,当计数器的值小于许可最大值时线程能够往下执行,反之线程则只能等待。我们深入分析了Semaphore的实现原理,它基于AQS同步器进行实现且提供了公平和非公平两种模式,并且我们对这两种模式的实现分别进行了分析。通过本文我们已经能够很深入清晰理解Semaphore的原理机制了。

- END -

Java多线程并发控制工具信号量Semaphore,实现原理及案例

本文分享自微信公众号 - 码农架构(iByteCoding)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
灯灯灯灯 灯灯灯灯
3年前
Java并发之Semaphore源码解析
Semaphore前情提要在学习本章前,需要先了解ReentrantLock源码解析,ReentrantLock源码解析里介绍的方法有很多是本章的铺垫。下面,我们进入本章正题Semaphore。从概念上来讲,信号量(Semaphore)会维护一组许可证用于限制线程对资源的访问,当我们有一资源允许线程并发访问,但我们希望能限制访问量,就可以用信号量对访问线程
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java多线程并发控制工具CountDownLatch,实现原理及案例
闭锁(CountDownLatch)是Java多线程并发中的一种同步器,它是JDK内置的同步器。通过它可以定义一个倒计数器,当倒计数器的值大于0时,所有调用await方法的线程都会等待。而调用countDown方法则可以让倒计数器的值减一,当倒计数器值为0时所有等待的线程都将继续往下执行。闭锁的主要应用场景是让某个或某些线程在某个运行节点上等待N个条件都
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
Java并发系列[6]
Semaphore(信号量)是JUC包中比较常用到的一个类,它是AQS共享模式的一个应用,可以允许多个线程同时对共享资源进行操作,并且可以有效的控制并发数,利用它可以很好的实现流量控制。Semaphore提供了一个许可证的概念,可以把这个许可证看作公共汽车车票,只有成功获取车票的人才能够上车,并且车票是有一定数量的,不可能毫无限制的发下去,这样就会导致公交车
Wesley13 Wesley13
3年前
Java并发系列(6)Semaphore源码分析
Semaphore(信号量)是JUC包中比较常用到的一个类,它是AQS共享模式的一个应用,可以允许多个线程同时对共享资源进行操作,并且可以有效的控制并发数,利用它可以很好的实现流量控制。Semaphore提供了一个许可证的概念,可以把这个许可证看作公共汽车车票,只有成功获取车票的人才能够上车,并且车票是有一定数量的,不可能毫无限制的发下去,这样就会导致公