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. 搭建nginx环境(参考腾讯云实验室)

    使用 yum 安装 Nginx: yum install nginx -y 修改 /etc/nginx/conf.d/default.conf,去除对 IPv6 地址的监听,可参考下面的代码示例: s ...

  2. HDU 4511 小明系列故事——女友的考验 ( Trie图 && DP )

    题意 :  给出编号从1 ~ n 的 n 个平面直角坐标系上的点,求从给出的第一个点出发到达最后一个点的最短路径,其中有两种限制,其一就是只能从编号小的点到达编号大的点,再者不能走接下来给出的 m 个 ...

  3. 6.10&&6.12考试反思

    考试结果:6.10AK 6.12:100(评测机)200(本地&&兼容评测机版) OI的考试做题流程无非是: 通读全部题目——>找一个最有把握/最简单的题——>分析思考—— ...

  4. APK文件结构和安装过程

    APK文件结构Android应用是用Java编写的,利用Android SDK编译代码,并且把所有的数据和资源文件打包成一个APK (Android Package)文件,这是一个后缀名为.apk的压 ...

  5. UVALive 6859 Points (凸包)

    Points 题目链接: http://acm.hust.edu.cn/vjudge/contest/130303#problem/E Description http://7xjob4.com1.z ...

  6. 利用python进行数据分析--pandas入门2

    随书练习,第五章  pandas入门2 # coding: utf-8 # In[1]: from pandas import Series,DataFrame import pandas as pd ...

  7. HTTP入门(二):用Chrome开发者工具查看 HTTP 请求与响应

    HTTP入门(二):用Chrome开发者工具查看 HTTP 请求与响应 本文简单总结HTTP的请求与响应. 本文主要目的是对学习内容进行总结以及方便日后查阅. 详细教程和原理可以参考HTTP文档(MD ...

  8. view组件

    view标签的属性值: hover-class:按下的点击态       属性值:字符串 如果:hover-class="none" 按下就没有点击态 hover-stop-pro ...

  9. 三十四、python中shutil模块的介绍

    '''A.shutil:高级的文件 文件夹 压缩包 处理模块''' import shutil '''1.copyfileobj(a1,a2,lenth):将文件内容拷贝到另一个文件中''' shut ...

  10. CSS- 层叠和继承

    层叠和继承 - 学习 Web 开发 | MDN 优先级 - CSS:层叠样式表 | MDN 继承 - CSS:层叠样式表 | MDN CSS 层叠 - CSS:层叠样式表 | MDN