前言

Semaphore也是JUC包中一个用于并发控制的工具类,举个常用场景的例子:有三台电脑五个人,每个人都要用电脑注册一个自己的账户,这时最开始只能同时有三个人操作电脑注册账户,这三个人中有人操作完了剩下的两个人才能占用电脑注册自己的账户。这就是Semaphore的经典使用场景,跟并发加锁有点像,只是我们的并发加锁同一时间只让有一个线程执行,而Semaphore的加锁控制是允许同一时间有指定数量的线程同时执行,超过这个数量就加锁控制。

一、使用样例

 public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 对比上面例子中的3台电脑
for (int i = 0; i < 5; i++) { // 对比上面例子中的5个人
new Thread(() -> {
try {
semaphore.acquire(1); // 注意acquire中的值可以传任意值>=0的整数
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " acquire 1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "release 1");
semaphore.release(1);
}).start();
}
}

执行结果为:

 Thread-0 acquire 1
Thread-2 acquire 1
Thread-1 acquire 1
Thread-1release 1
Thread-2release 1
Thread-0release 1
Thread-4 acquire 1
Thread-3 acquire 1
Thread-4release 1
Thread-3release 1

可以看到同一时间只有三个线程获取到了锁,这三个执行完释放了之后,剩下两个菜获取锁执行。下面看看源码是如何实现的。

二、源码实现

1、Semaphore构造器

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

可以看到,Semaphore有两个构造器,一个是只传数值默认非公平锁,另一个可指定用公平锁还是非公平锁。permits最终还是赋值给了AQS中的state变量。

2、acquire(1)方法

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

此方法同样调用了AQS中的模板方法:

 public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

1)、查看tryAcquireShared的实现方法

先看非公平锁的获取:

 final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires; // 如果remaining是负的,说明当前剩余的信号量不够了,需要阻塞
if (remaining < 0 ||
compareAndSetState(available, remaining)) // 如果remaining<0则直接return,不会走CAS;如果大于0,说明信号量还够,可走CAS将信号量减掉,成功则返回大于0的remaining
return remaining;
}
}

再看公平锁的获取:

 protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors()) // 判断是不是在队首,不是的话直接返回-1
return -1;
int available = getState(); // 后面逻辑通非公平锁的获取逻辑
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

可以看到,不管非公平锁和公平锁,加锁时都是先判断当前state够不够减的,如果减出负数返回获取锁失败,是正数才走CAS将原信号量扣掉,返回获取锁成功。加锁时一个减state的过程。

2)、doAcquireSharedInterruptibly

此方法还是AQS中的实现,逻辑重复,就不再说明了。

3、release(1)方法

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

同样调用了AQS中的模板方法releaseShared:

 public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

其中tryReleaseShared的实现在Semaphore类的Sync中,如下所示:

 protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases; // 用当前state加上要释放的releases
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next)) // 用CAS将state加上
return true;
}
}

另一个方法doReleaseShared之前看过,此处就不赘述了。

三、小结

Semaphore信号量类基于AQS的共享锁实现,有公平锁和非公平锁两个版本。它的加锁与释放锁的不同之处在于和普通的加锁释放锁反着,ReentrantLock和ReentrantReadWriteLock中都是加锁时state+1,释放锁时state-1,而Semaphore中是加锁时state减,释放锁时state加。

另外,如果它还可以acquire(2) 、release(1),即获取的和释放的信号量可以不一致,只是需要注意别释放的信号量太少导致后续任务获取不到足够的量而永久阻塞。

AQS系列(六)- Semaphore的使用及原理的更多相关文章

  1. java多线程系列(六)---线程池原理及其使用

    线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...

  2. java基础解析系列(六)---深入注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...

  3. java基础解析系列(六)---注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...

  4. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  5. Java并发编程系列-(8) JMM和底层实现原理

    8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...

  6. AQS系列(七)- 终篇:AQS总结

    前言 本文是对之前AQS系列文章的一个小结,首先看看以下几个问题: 1.ReentrantLock和ReentrantReadWriteLock的可重入特性是如何实现的? 2.哪个变量控制着锁是否被占 ...

  7. CountDownLatch、CyclicBarrier和Semaphore 使用示例及原理

    备注:博客园的markDown格式支持的特别不友好.也欢迎查看我的csdn的此篇文章链接:CountDownLatch.CyclicBarrier和Semaphore 使用示例及原理 CountDow ...

  8. Bing Maps进阶系列六:使用Silverlight剪切(Clip)特性实现Bing Maps的迷你小地图

    Bing Maps进阶系列六:使用Silverlight剪切(Clip)特性实现Bing Maps的迷你小地图 Bing Maps Silverlight Control虽然为我们提供了简洁.方面的开 ...

  9. AQS系列(一)- ReentrantLock的加锁

    前言 AQS即AbstractQueuedSynchronizer,是JUC包中的一个核心抽象类,JUC包中的绝大多数功能都是直接或间接通过它来实现的.本文是AQS系列的第一篇,后面会持续更新多篇,争 ...

  10. 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念

    深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...

随机推荐

  1. 20191017-6alpha week 2/2 Scrum立会报告+燃尽图 05

    此作业要求参见https://edu.cnblogs.com/campus/nenu/2019fall/homework/9802 一.小组情况 队名:扛把子 组长:迟俊文 组员:宋晓丽 梁梦瑶 韩昊 ...

  2. 百度杯 black_hole复现

    在这次复现中,经历了太多挫折. 刚刚开始的时候本地调试 get不到shell,就很疑问,而且不会爆破,想学下怎么爆破出那个0x05, 后来问了位师傅 ,他说用retdl_solve 试试,我就跑去学了 ...

  3. HTML学习 day04

    1.字体.文本声明 声明语句必须要包含在{}号之中: 属性和属性值之间用":"分隔: 当有多个属性时,用":"进行区分: 在书写属性时属性之间使用空格.换行等, ...

  4. C#连接SAP【生产系统与ERP对接】

    企业如果上了ERP系统,比如SAP.用友.金蝶或者E10等等,只需要ERP里面提供相应的接口,则可以直接将PMC创建的工单信息抛转至 MTS 系统,当该工单生产完成之后,MTS 将完成数据回传至 ER ...

  5. 第三个视频作品《小白快速入门greenplum》上线了

    1.场景描述 第三个视频作品出炉了,<小白快速入门greenplum>上线了,有需要的朋友可以直接点击链接观看.(如需购买,请通过本文链接购买) 2. 课程内容 课程地址:https:// ...

  6. 图解 Spring:HTTP 请求的处理流程与机制【3】

    3. HTTP 请求在 Web 应用中的处理流程 在穿越了 Web 容器之后,HTTP 请求将被投送到 Web 应用,我们继续以 Tomcat 为例剖析后续流程.Web 容器与 Web 应用的衔接是通 ...

  7. petri 网理论与研究(第一节140915)

    成绩 :70 大作业 和 30 的最后讨论 petri 是一个人的名字. 网状结构的信息流模型,和自动机有点像 理论体系发展比较慢  应用很远 1      EN,P/T,Pr/T,CPN,关系网……

  8. 配置防盗链、访问控制Directory和FilesMatch

    5月31日任务 课程内容: 11.25 配置防盗链11.26 访问控制Directory11.27 访问控制FilesMatch扩展几种限制ip的方法 http://ask.apelearn.com/ ...

  9. ios webp转换jpg

    在项目开发的过程中,遇到了一个问题,就是webp的图片,先解释一下webp是啥,webp是谷歌开发的一种旨在加快图片加载速度的图片格式.图片压缩体积大约只有JPEG的2/3,说白了就是省空间,特别对于 ...

  10. 使用 SecureRandom 产生随机数采坑记录

    公众号「码海」欢迎关注 背景 我们的项目工程里经常在每个函数需要用到 Random 的地方定义一下 Random 变量(如下) public void doSomethingCommon() { Ra ...