前言

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. php为什么要用swoole?

    最近两个月一直在研究 Swoole,那么借助这篇文章,我希望能够把 Swoole 安利给更多人.虽然 Swoole 可能目前定位是一些高级 phper 的玩具,让中低级望而生畏,可能对一些应用场景也一 ...

  2. RxJS入门

    一.RxJS是什么? 官方文档使用了一句话总结RxJS: Think of RxJS as Lodash for events.那么Lodash主要解决了什么问题?Lodash主要集成了一系列关于数组 ...

  3. 驰骋工作流系统-Java共工作流引擎配置定时任务

    关键词:工作流定时任务  流程引擎定时任务设置  工作流系统定时任务配置  开源工作流引擎 开源工作流系统 一.定时任务的作用 发送邮件,发送短信. 处理节点自动执行的任务.比如:一个节点的待办工作是 ...

  4. 题解 P3954 【成绩】

    题目评级: ★ (水题) 内容及算法: 无,简单模拟计算即可 代码: /** *@author little_frog */ #include <bits/stdc++.h> using ...

  5. 基于 HTML5 + WebGL 的宇宙(太阳系) 3D 可视化系统

    前言 近年来随着引力波的发现.黑洞照片的拍摄.火星上存在水的证据发现等科学上的突破,以及文学影视作品中诸如<三体>.<流浪地球>.<星际穿越>等的传播普及,宇宙空间 ...

  6. 在 Kubernetes 集群快速部署 KubeSphere 容器平台

    KubeSphere 不仅支持部署在 Linux 之上,还支持在已有 Kubernetes 集群之上部署 KubeSphere,自动纳管 Kubernetes 集群的已有资源与容器. 前提条件 Kub ...

  7. php 第1讲 html介绍 html运行原理①

    1. html (hypertext mark-up language )是 超文本编辑语言,主要的用处是做网页,可以在网页上显示文字.图形.动画.视频... “标记“有时候也称之为“元素” 动态网页 ...

  8. Android 子线程更新UI 异常

    众所周知,Android是不可以在子线程中直接更新UI的,需要借助Handler或者View.post(Runnable runnable)或者runOnUIThread(Runnable runna ...

  9. Java 从入门到进阶之路(十一)

    之前的文章我们介绍了一下 Java 中的继承,接下来我们继续看一下 Java 中的继承. 在有些时候,我们通过类继承的方式可以获取父类的方法,但是有些时候父类为我们提供的方法并不完全符合我们的需求,这 ...

  10. 带着canvas去流浪系列之一:绘制柱状图

    [摘要] 学习使用canvasAPI来实现数据可视化. 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 任务说明 使用原生canvasAPI绘制柱 ...