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);
}
}
  1. 封装一个Node节点,加入队列尾部
  2. 在无限循环中,如果当前节点是头节点,就尝试获取信号
  3. 不是头节点,在经过节点状态判断后,挂起当前线程

二、release()释放信号  

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 1
doReleaseShared(); // 2
return true;
}
return false;
}
  1. 更新state加一
  2. 唤醒等待队列头节点线程

  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实现原理的更多相关文章

  1. Java并发编程原理与实战二十八:信号量Semaphore

    1.Semaphore简介 Semaphore,是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类. 所谓Semaphore即 信号量 的意思. 这个叫法并不能很好地 ...

  2. linux内核剖析(十)进程间通信之-信号量semaphore

    信号量 什么是信号量 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有. 信号量的值为正的时候,说明它空闲.所测试的线程可以锁定而使用它.若为0,说明它被占用,测试的线 ...

  3. Java并发(十五):并发工具类——信号量Semaphore

    先做总结: 1.Semaphore是什么? Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源. 把它比作是控制流量的红绿灯,比如XX马路要 ...

  4. python线程信号量semaphore(33)

    通过前面对 线程互斥锁lock /  线程事件event / 线程条件变量condition / 线程定时器timer 的讲解,相信你对线程threading模块已经有了一定的了解,同时执行多个线程的 ...

  5. C# 多线程之一:信号量Semaphore

    通过使用一个计数器对共享资源进行访问控制,Semaphore构造器需要提供初始化的计数器(信号量)大小以及最大的计数器大小 访问共享资源时,程序首先申请一个向Semaphore申请一个许可证,Sema ...

  6. 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  7. 互斥锁Mutex与信号量Semaphore的区别

    转自互斥锁Mutex与信号量Semaphore的区别 多线程编程中,常常会遇到这两个概念:Mutex和Semaphore,两者之间区别如下: 有人做过如下类比: Mutex是一把钥匙,一个人拿了就可进 ...

  8. 信号量 Semaphore

    一.简介         信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用,负责协调各个线程, 以保证它们能够正确.合理的使用公共资源. Semaphore可以控制某个资源可被同时 ...

  9. windows核心编程-信号量(semaphore)

    线程同步的方式主要有:临界区.互斥区.事件.信号量四种方式. 前边讲过了互斥器线程同步-----windows核心编程-互斥器(Mutexes),这章我来介绍一下信号量(semaphore)线程同步. ...

随机推荐

  1. Eclipse 中的 parameter参数,property属性,preference首选项 区别

    parameter参数 1.配置框架 web.xml <init-param> <param-name>contextConfigLocation</param-name ...

  2. VS2015编译问题:模块对于 SAFESEH 映像是不安全的

    严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK2026 模块对于 SAFESEH 映像是不安全的. zlibvc C:\MyDirectory\test2\zlib-\contrib\ ...

  3. Linux内核设计与实现 总结笔记(第十五章)进程地址空间

    一.地址空间 进程地址空间由进程可寻址的虚拟内存组成,内核允许进程使用这种虚拟内存中的地址. 每个进程都有一个32位或64位的平坦地址空间,空间的具体大小取决于体系结构.“平坦”指的是地址空间范围是一 ...

  4. PHP文件的上传和下载

    1.使用PHP的创始人 Rasmus Lerdorf 写的APC扩展模块来实现(http://pecl.php.net/package/apc) APC实现方法: 安装APC,参照官方文档安装,可以使 ...

  5. 七牛云对象存储kodo使用体验

    在这里,我使用了七牛云的对象存储Kodo,和阿里云的OSS,还有腾讯云的COS是同样的产品 oss相关术语 包依赖关系解决 unrecognized import path "golang. ...

  6. CodeForces 1198D 1199F Rectangle Painting 1

    Time limit 1000 ms Memory limit 262144 kB 解题思路 一堆循环嵌套的那种dp,不好想.但是可以搜啊,很暴力的.记忆化一下就好. 我们定义搜索函数\(\text{ ...

  7. 【学习心得】Link-cut Tree

    Link-cut Tree是一种支持改变树(森林)的形态(link和cut),同时维护树的路径上节点信息的数据结构.lct通过splay来维护每次的perferred path,说白了就是一个动态的树 ...

  8. 01 Netty是什么?

    01 Netty是什么? IO编程 我们简化下场景:客户端每隔两秒发送一个带有时间戳的 "hello world" 给服务端,服务端收到之后打印. 为了方便演示,下面例子中,服务端 ...

  9. InfluxDB安装使用

    influxdb简介   启动步骤 服务启停:sudo service influxdb start/stop/restart 安装过程: 1.增加yum源 cat <<EOF | sud ...

  10. xml json mongo

    w wuser@ubuntu:~/apiamzpy$ sudo pip install xmljson