信号量Semaphore实现原理
Semaphore用于管理信号量,在并发编程中,可以控制返访问同步代码的线程数量。Semaphore在实例化时传入一个int值,也就是指明信号数量。主要方法有两个:acquire()和release()。acquire()用于请求信号,每调用一次,信号量便少一个。release()用于释放信号,调用一次信号量加一个。信号量用完以后,后续使用acquire()方法请求信号的线程便会加入阻塞队列挂起。本篇简单分析Semaphore的源码,说明其实现原理。
Semaphore对于信号量的控制是基于AQS(AbstractQueuedSynchronizer)来做的。Semaphore有一个内部类Sync继承了AQS。而且Semaphore中还有两个内部类FairSync和NonfairSync继承Sync,也就是说Semaphore有公平锁和非公平锁之分。以下是Semaphore中内部类的结构:
看一下Semaphore的两个构造函数:
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
默认是非公平锁。两个构造方法都必须传int permits值。
这个int值在实例化内部类时,被设置为AQS中的state。
Sync(int permits) {
setState(permits);
}
一、acquire()获取信号
内部类Sync调用AQS中的acquireSharedInterruptibly()方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < )
doAcquireSharedInterruptibly(arg);
}
- 调用tryAcquireShared()方法尝试获取信号。
- 如果没有可用信号,将当前线程加入等待队列并挂起
tryAcquireShared()方法被Semaphore的内部类NonfairSync和FairSync重写,实现有一些区别。
NonfairSync.tryAcquireShared()
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < ||
compareAndSetState(available, remaining))
return remaining;
}
}
可以看到,非公平锁对于信号的获取是直接使用CAS进行尝试的。
FairSync.tryAcquireShared()
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -;
int available = getState();
int remaining = available - acquires;
if (remaining < ||
compareAndSetState(available, remaining))
return remaining;
}
}
- 先调用hasQueuedPredecessors()方法,判断队列中是否有等待线程。如果有,直接返回-1,表示没有可用信号
- 队列中没有等待线程,再使用CAS尝试更新state,获取信号
再看看acquireSharedInterruptibly()方法中,如果没有可用信号加入队列的方法doAcquireSharedInterruptibly()
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // 1
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) { // 2
int r = tryAcquireShared(arg);
if (r >= ) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && // 3
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 封装一个Node节点,加入队列尾部
- 在无限循环中,如果当前节点是头节点,就尝试获取信号
- 不是头节点,在经过节点状态判断后,挂起当前线程
二、release()释放信号
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 1
doReleaseShared(); // 2
return true;
}
return false;
}
- 更新state加一
- 唤醒等待队列头节点线程
tryReleaseShared()方法在内部类Sync中被重写
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;
}
}
这里也就是直接使用CAS算法,将state也就是可用信号,加1。
看看Semaphore具体的使用示例:
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(, ,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
//信号总数为5
Semaphore semaphore = new Semaphore();
//运行10个线程
for (int i = ; i < ; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
//获取信号
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "获得了信号量,时间为" + System.currentTimeMillis());
//阻塞2秒,测试效果
Thread.sleep();
System.out.println(Thread.currentThread().getName() + "释放了信号量,时间为" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放信号
semaphore.release();
}
}
});
}
threadPool.shutdown();
}
代码结果为:
pool--thread-2获得了信号量,时间为1550584196125
pool--thread-1获得了信号量,时间为1550584196125
pool--thread-3获得了信号量,时间为1550584196125
pool--thread-4获得了信号量,时间为1550584196126
pool--thread-5获得了信号量,时间为1550584196127
pool--thread-2释放了信号量,时间为1550584198126
pool--thread-3释放了信号量,时间为1550584198126
pool--thread-4释放了信号量,时间为1550584198126
pool--thread-6获得了信号量,时间为1550584198126
pool--thread-9获得了信号量,时间为1550584198126
pool--thread-8获得了信号量,时间为1550584198126
pool--thread-1释放了信号量,时间为1550584198126
pool--thread-10获得了信号量,时间为1550584198126
pool--thread-5释放了信号量,时间为1550584198127
pool--thread-7获得了信号量,时间为1550584198127
pool--thread-6释放了信号量,时间为1550584200126
pool--thread-8释放了信号量,时间为1550584200126
pool--thread-10释放了信号量,时间为1550584200126
pool--thread-9释放了信号量,时间为1550584200126
pool--thread-7释放了信号量,时间为1550584200127
可以看到,最多5个线程获得信号,其它线程必须等待获得信号的线程释放信号。
信号量Semaphore实现原理的更多相关文章
- Java并发编程原理与实战二十八:信号量Semaphore
1.Semaphore简介 Semaphore,是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类. 所谓Semaphore即 信号量 的意思. 这个叫法并不能很好地 ...
- linux内核剖析(十)进程间通信之-信号量semaphore
信号量 什么是信号量 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有. 信号量的值为正的时候,说明它空闲.所测试的线程可以锁定而使用它.若为0,说明它被占用,测试的线 ...
- Java并发(十五):并发工具类——信号量Semaphore
先做总结: 1.Semaphore是什么? Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源. 把它比作是控制流量的红绿灯,比如XX马路要 ...
- python线程信号量semaphore(33)
通过前面对 线程互斥锁lock / 线程事件event / 线程条件变量condition / 线程定时器timer 的讲解,相信你对线程threading模块已经有了一定的了解,同时执行多个线程的 ...
- C# 多线程之一:信号量Semaphore
通过使用一个计数器对共享资源进行访问控制,Semaphore构造器需要提供初始化的计数器(信号量)大小以及最大的计数器大小 访问共享资源时,程序首先申请一个向Semaphore申请一个许可证,Sema ...
- 经典线程同步 信号量Semaphore
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- 互斥锁Mutex与信号量Semaphore的区别
转自互斥锁Mutex与信号量Semaphore的区别 多线程编程中,常常会遇到这两个概念:Mutex和Semaphore,两者之间区别如下: 有人做过如下类比: Mutex是一把钥匙,一个人拿了就可进 ...
- 信号量 Semaphore
一.简介 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用,负责协调各个线程, 以保证它们能够正确.合理的使用公共资源. Semaphore可以控制某个资源可被同时 ...
- windows核心编程-信号量(semaphore)
线程同步的方式主要有:临界区.互斥区.事件.信号量四种方式. 前边讲过了互斥器线程同步-----windows核心编程-互斥器(Mutexes),这章我来介绍一下信号量(semaphore)线程同步. ...
随机推荐
- 【bzoj2733】[HNOI2012]永无乡
题目描述: 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛 到 ...
- PHPExcel组件编程spl_autoload_register
E:\html\tproject\framework\modules\common\vendor\PHPExcel\Classes\PHPExcel.php <?php /** PHPExcel ...
- POJ 1363 Rails(栈)
题目代号:POJ 1363 题目链接:http://poj.org/problem?id=1363 题目原题: Rails Time Limit: 1000MS Memory Limit: 100 ...
- Internet History, Technology, and Security(week6)——Technology: Transport Control Protocol(TCP)
前言: 这周开始学习分层网络模型的第三层,传输层. Transport/Reliability: Transport Layer TCP层的目的是补偿IP层中可能出现的错误,并充分利用可用资源.由于I ...
- 3D Computer Grapihcs Using OpenGL - 12 Rotation Matrix
为了证明我们上节渲染出来的是一个立方体而不是一个平面,我们决定将它旋转一定角度,这样我们就需要一个旋转矩阵(也属于ModelTransformMatrix的一部分) 上一节我们的ModelTransf ...
- SpringBoot:运行原理探究
西部开源-秦疆老师:基于SpringBoot 2.1.6 的博客教程 秦老师交流Q群号: 664386224 未授权禁止转载!编辑不易 , 转发请注明出处!防君子不防小人,共勉! SpringBoot ...
- matlab 重命名文件和文件夹
1.查看文件存在 dir() 若存在,返回文件信息 dir(‘test.txt’) %查看当前目录是否存在test.txt文件 dir(‘C:\test.txt’) %查看指定目录是否存在 ...
- React-Native 之 GD (三)近半小时热门
1.设置页面跳转 半小时热门组件 GDHalfHourHot.js /** * 近半小时热门 */ import React, { Component } from 'react'; import ...
- 将String转化成Stream,将Stream转换成String, C# Stream 和 byte[] 之间的转换(文件流的应用)
static void Main( string[] args ) { string str = "Testing 1-2-3"; //convert string 2 strea ...
- 应用安全-CMF/CMS漏洞整理
CMS识别 云悉 http://whatweb.bugscaner.com/batch.html CakePHP CakePHP <= / Cache Corruption Exploit 2 ...