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. php shuffle()函数 语法

    php shuffle()函数 语法 作用:把数组中的元素按随机顺序重新排序:富瑞华 语法:shuffle(array) 参数: 参数 描述 array 必需.规定要使用的数组. 说明:若成功则返回 ...

  2. php array_push()函数 语法

    php array_push()函数 语法 作用:向第一个参数的数组尾部添加一个或多个元素(入栈),然后返回新数组的长度.博智达 语法:array_push(array,value1,value2.. ...

  3. Nginx动静分离-tomcat

    一.动静分离 1.通过中间件将动态请求和静态请求分离. 2.为什么? 分离资源,减少不必要的请求消耗,减少请求延时. 3.场景 还可以利用php,fastcgi,python 等方式 处理动态请求 # ...

  4. 6.12友谊赛T4城市交通费题解

    与普通的最短路径不同的是,题目中新引入了一个计入总体的费用——城市建设费.由于城市建设费由整体的某最大值决定,导致解没有最优子结构的性质,给思维带来难度. 既然最棘手的是城市建设费,我们就对它分类讨论 ...

  5. [BZOJ1547]周末晚会:Burnside引理+DP

    分析 Attention!这道题的模数是\(1e8+7\). 注意到循环同构会被认为是同一种方案,我们可以把顺时针旋转每个人的位置作为置换,容易发现这些置换一定会形成一个置换群,于是题目所求的所有合法 ...

  6. POJ 1434 Fill the Cisterns! (模拟 or 二分)

    Fill the Cisterns! 题目链接: http://acm.hust.edu.cn/vjudge/contest/129783#problem/F Description During t ...

  7. nodejs工作大全

    1.修改文件夹中图片的名称 var fs = require('fs');var fileDirectory = "F:\\zdw\\修改文件夹名称\\newFile";var n ...

  8. equals深入理解

    package cn.galc.test; public class TestEquals { public static void main(String[] args) { /** * 这里使用构 ...

  9. python2.7+appium环境搭建

    实现android自动化 目录 1.环境准备.安装包准备 2.安装 3.验证环境搭建成功 1.环境准备.安装包准备 第一步:环境准备: java环境 python环境 第二步:准备安装包 Node.j ...

  10. Week 9 - 638.Shopping Offers - Medium

    638.Shopping Offers - Medium In LeetCode Store, there are some kinds of items to sell. Each item has ...