多线程分析之Semaphore
Semaphore分析由来
网上看了许多讲解Semaphore的,用Semaphore来实现顺序打印字母,但是可能大家都没有清楚具体的原因,所以来给大家分析下为什么可以使用Semaphore来实现顺序打印字母顺序。
Semaphore源码分析
先打开JDK8源码中的Semaphore,可以看到Semaphore是通过继承AQS来现实功能(AQS,Doug Lea大神重写并发包的核心,这个默认自己看过哈,其实蛮简单,核心原理:通过模板方法,完成流程调用,让子类实现具体方法,然后实现不同功能)。
说正事,我们贴出一张Semaphore的层级关系图。

在Semaphore中主要是Sync类实现AbstractQueuedSynchronizer,然后Sync又有两个实现类,分别是FaireSync和NonfairSync,即公平锁和非公平锁。我们来看下Sync中的部分源码:
/**
* Synchronization implementation for semaphore. Uses AQS state
* to represent permits. Subclassed into fair and nonfair
* versions. (使用AQS的state变量来代表许可,注释这段还是蛮重要)
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L; // 在构造函数中传入许可数
Sync(int permits) {
setState(permits);
}
// 获取许可数量
final int getPermits() {
return getState();
}
// 非公平锁获取许可方式
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 归还许可,更新可用许可数量
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
// 减少许可, 获取时需要减少许可数量
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
} }
在类中,没有特别难的方法,都是都过CAS来进行操作,用AQS中的state来当作许可。好了, 有了这一部分基础,我们可以去看看大家是如何使用Semaphore来实现顺序打印的。还是先为大家贴上代码:
/**
* <br>使用信号量顺序打印</br>
*
* @author lifacheng
* @version 1.0
* @date 2019/6/18 11:08
* @since 1.0
*/
public class Thread11 {
private static Semaphore semaphore1 = new Semaphore(0);
private static Semaphore semaphore2 = new Semaphore(1); Thread t1, t2; int count = 10; public Thread11() {
t1 = new Thread() {
public void run() {
try {
int i = 0;
while(i++ < count) {
//获得许可
semaphore2.acquire();
System.out.print("A");
//初始化许可为0,处于阻塞,当release获取许可、
semaphore1.release();
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
t2 = new Thread() {
public void run() {
try {
int i = 0;
while(i++ < count) {
semaphore1.acquire();
System.out.print("B");
semaphore2.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
} public void run() {
t1.start();
t2.start();
} public static void main(String args[]) throws Exception {
Thread11 t = new Thread11();
t.run();
}
}
在代码一开始,初始化了两个信号量,分别为:semaphore1,semaphore2,semaphore1的许可书为0个,semaphore2的许可书为1个。
acquire()源码分析
在线程t1的run()方法中,semaphore2执行了acquire()方法,我们打开源码看看这段逻辑是什么,

代码调用了sync中的acquireSharedInterruptibly()方法,此时我们要注意,传入的参数是1个(这个很重要)。我们接着往下找,找到这个是AQS中实现的方法。

我们找到具体的实现类中的方法,即tryAcquireShare(arg)这个在Semaphore中实现的方法。(在tryAcquireShare(arg)<0时,会进去doAcquireSharedInterruptibly()方法中,当获取小于0会处于阻塞状态)
)

我们可以看到有Semaphore有两个实现类,分别是公平和非公平两种实现,我们点进去看看到底有何不同。
公平:
非公平:
我们看到,公平就比非公平多了一个判断,这个判断就是判断是否是头节点(就不点进去看了哈)。
至此,我们可以看到,acquire()方法具体实现就是获取了一个许可。我们接着看代码,然后输出字符A,semaphore1执行了release()方法。
release()方法分析
我们点进release方法查看,

按照和acquire()的相同的逻辑,最后找到如上这段代码,即在执行release()方法时,会增加1个许可。
代码逻辑
自此我们知道代码的逻辑了,来具体分析下代码。
当线程t1和t2同时开始运行,这个t1开始执行获取到许可,然后输出A,这是就算t2拿到CPU执行权,由于初始化许可数为0,这时acquire()方法获取不到许可,处于阻塞状态。只有当t1中的semaphore1执行了release()方法时,才会增加一个许可,t2获取到CPU执行权后才会执行。假设此时t1又获取到CPU执行权,但是由于只有一个许可,开始获取过许可,再此获取会失败,也会处于阻塞状态,只有t2线程中semaphore2执行了release()方法才会增加一个许可,然后t1才会再次获取成功并执行。
代码充分使用了许可数量来控制线程的执行,当线程执行时,相互唤醒,增加许可数量。有点像wait和notify的概念,但是更高级,我们可以增加semaphore来控制多个线程执行顺序。 这下次就分析清楚了,为什么初始化0个许可数的semaphore仍然可以用来控制。可以说相当神奇。我们也对AQS的强大有了一个小小的了解。
总结
AQS的强大一时半会说不清楚,希望大家多看看源码,结合百家之所长,来提升自己。
多线程分析之Semaphore的更多相关文章
- C#多线程--信号量(Semaphore)
百度百科:Semaphore,是负责协调各个线程, 以保证它们能够正确.合理的使用公共资源.也是操作系统中用于控制进程同步互斥的量. Semaphore常用的方法有两个WaitOne()和Releas ...
- 【JUC】JDK1.8源码分析之Semaphore(六)
一.前言 分析了CountDownLatch源码后,下面接着分析Semaphore的源码.Semaphore称为计数信号量,它允许n个任务同时访问某个资源,可以将信号量看做是在向外分发使用资源的许可证 ...
- Android多线程分析之五:使用AsyncTask异步下载图像
Android多线程分析之五:使用AsyncTask异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处 在本系列文章的第一篇<An ...
- Android多线程分析之四:MessageQueue的实现
Android多线程分析之四:MessageQueue的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前面两篇文章<Androi ...
- Android多线程分析之三:Handler,Looper的实现
Android多线程分析之三:Handler,Looper的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前文<Android多 ...
- Android多线程分析之二:Thread的实现
Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前文<Android多线程分析之一 ...
- Android多线程分析之一:使用Thread异步下载图像
Android多线程分析之一:使用Thread异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处 打算整理一下对 Android F ...
- Android四个多线程分析:MessageQueue实现
Android四个多线程分析:MessageQueue的实现 罗朝辉 (http://blog.csdn.net/kesalin) CC 许可,转载请注明出处 在前面两篇文章<Android多线 ...
- Android多线程分析之中的一个:使用Thread异步下载图像
Android多线程分析之中的一个:使用Thread异步下载图像 罗朝辉 (http://blog.csdn.net/kesalin) CC 许可.转载请注明出处 打算整理一下对 Android Fr ...
随机推荐
- go开发工具goclipse的安装
(1) 安装Eclipse 建议下载Eclipse时直接下载"Eclipse IDE for Java Developers"的package,而不要下载较小的Installer. ...
- 创业学习---《如何预判创业可行性》--B-1.预判模块---HHR计划---以太一堂
<如何预判创业可行性>----对创业进行占卜 一,<开始学习> 1,预热思考题: (1)预判一个模式的可行性.你有一个朋友要创业,给你讲了他的创业计划,你帮他判断一下是否靠谱. ...
- 《掌握融资必备知识》---创业学习---训练营第一课---HHR---
一,<开始学习> 1,四个思考题: (1)从你决定开始融资,到你拿到钱,你都需要经历哪些环节? (2)你知道投资机构内部的工作流程吗? (3)融资最好的时机是什么时候? (4)创投圈的专业 ...
- 计划任务cron,date,时间同步ntp,chrony
取出磁盘利用率最大值 ,写个脚本,做判断,只要快满了,就报警 计划周期性执行的任务提交给crond,到指定时间会自动运行 系统cron任务:系统维护作业 /etc/crontab 用户cron任务: ...
- HDU1495 非常可乐(BFS/数论)
大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为.因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多.但see ...
- [02]Sort选择排序
选择排序 算法速度:通过大O表示法表示,O(n),n是操作数,表示算法执行的次数: 数组:是有序的元素序列:若将有限个类型相同的变量的集合命名,那么这个名称为数组名: 链表:是一种物理存储单元上非连续 ...
- Edge Beta Android版更新已启用新图标
导读 微软Edge Beta Android版更新已启用新图标设计 IT之家消息 适用于Android的Microsoft Edge Beta已于近日获得更新,最显著的特征就是使用了新图标设计.该图标 ...
- java web 学生信息录入
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
- 「CF383C Propagating tree」
这应该属于一个比较麻烦的数据结构处理树上问题. 题目大意 给出一颗根节点编号为 \(1\) 的树,对于一个节点修改时在它的子树中对于深度奇偶性相同的节点加上这个权值,不同则减去这个值,单点查询. 分析 ...
- python3.6.5修改print的颜色
开头部分:\033[显示方式;前景色;背景色m +想要输出的内容:\033[0m 注意:开头部分的三个参数:显示方式,前景色,背景色是可选参数,具体参数效果见下文,可以只写其中的某一个:参数 ...