Semaphore是一个计数的信号量。从概念上来说,信号量维持一组许可(permits)。acquire方法在必须的时候都会堵塞直到有一个许可可用,然后就会拿走这个许可。release方法加入一个许可,会有可能释放一个堵塞中的获取者(acquirer)。然而,Semaphore没有使用真实的许可对象,仅仅是保持一个可用计数而且採取对应的行为。

信号量一般用于限制能够訪问一些(物理上或者逻辑上)的资源的并发线程数。

信号量初始化为1的时候,意味着它最多仅仅有一个同意可用,这样就能作为相互排斥独占锁使用。这样的很多其它地被称为二进制信号量(binary semaphore),由于它仅仅有两个状态:一个许可可用,或者0个许可可用。当使用这样的方式的时候,二进制信号量就有这样的属性(不像大部分锁的实现):锁能够被拥有者(就如信号量没有拥有者的概念)之外的另外线程释放。这样的属性在某些特殊的上下文中非常实用,比如死锁恢复。

    类的构造函数可选地接受一个fairness參数。当设为false的时候,该类就不会保证线程获取许可的顺序。特别地,插队是同意的,也就是说,线程调用acquire方法能够在另外的等待线程之前分配许可————逻辑上新线程会把自己放在等待线程队列头。当fairness设为true,信号量就保证调用acquire方法的线程会以它们调用方法的处理顺序来获取许可(FIFO)。注意FIFO的顺序决定特指在这些方法的内部运行点。因此,有可能一条线程在另外一条线程之前调用了acquire方法,但实际顺序会在另外一条线程之后,相同也适用于函数返回的先后顺序。相同要注意非超时版本号tryAcquire方法不会遵循fairness设置,会立即获取不论什么可用的许可。

    一般来说,信号量用来控制资源訪问的话,就应该被初始化为公平(fairness设为true),这样能够确保没有线程会在訪问资源的时候饿坏。当使用在其它同步控制的情况下使用信号量,非公平的吞吐量优势一般优于公平的考虑。



    事实上整体上来看,fairness參数以及状态量的概念非常接近AQS(AbstractQueuedSynchronizer)提供的功能,因此大家也应该猜到Semaphore的内部实现也是通过一个继承AQS的内部类实现接口功能。接下来我们细致看看内部的实现。

详细实现

先来看看Semaphore的构造函数:

    public Semaphore(int permits) {
sync = new NonfairSync(permits);
} public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

能够看到构造函数与ReentrantLock实现类似,都是依照fair參数分配创建不同的锁类,再来看看Semaphore的acquire和release的接口实现

    public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} public void release() {
sync.releaseShared(1);
}

能够看到acquire和release的实现都是调用内部类Sync的方法实现,当然了,这些方法也就是AQS提供出来的获取和释放共享锁接口。接下来看看整个实现里最基本的内部类Sync的相关实现:

    abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L; Sync(int permits) {
setState(permits);
} 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;
}
} //省略一些次要方法
} /**
* 非公平版本号
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L; NonfairSync(int permits) {
super(permits);
} protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
} /**
* 公平版本号
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L; FairSync(int permits) {
super(permits);
} protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}

为了方便了解主要逻辑,Sync类省略掉一些次要的方法。非公平版本号NonfairSync类和公平版本号FairSync类都继承于Sync类,Sync类继承于AQS类,NonfairSync和FairSync类都有相同的tryReleaseShared实现,仅仅只是在tryAcquireShared实现上有略微不同。

    先来看看非公平类NonfairSync实现。tryAcquireShared调用的是Sync类的nonfairTryAcquireShared方法,方法的实现相当简单,仅仅是在循环内推断当前锁状态值减去请求值acquires后,假设remaining < 0(则表示此次acquire失败,直接返回负值remaining就可以)或者remaining >=0 时,compareAndSetState成功(表示此次acquire成功,直接返回大于等于0的remaining就可以),假设CAS失败,则继续循环重试,直到当中一种情况发生。

    再来看看公平类FairSync的实现。tryAcquireShared直接被重写,与非公平类版本号对照,添加了hasQueuedPredecessors的推断,该方法在AQS中表示是否有结点在当前的等待队列前排在自己前面,假设返回true,则表示当前线程须要进入等待队列,直接返回-1表示acquire失败。

    tryReleaseShared的实现也非常easy,也是一个循环里不断CAS把锁状态添加请求的releases就可以。

    Semaphore还有其他一些辅助方法,事实上现也都是简单地调用内部类Sync的方法,这里便不再赘述。



总结

    整体来看,AQS的锁状态值就等于Semaphore的许可量,acquire的实现就是把当前锁状态值,也就是许可量减去相应值,release的实现就是把锁状态值添加相应值就可以。整个实现结构和ReentrantLock类似,但没有了重入的逻辑,并且实现更是相对简单,理解起来应该没有难度。

Semaphore实现Andoird版源代码剖析的更多相关文章

  1. STL源代码剖析——基本算法stl_algobase.h

    前言 在STL中.算法是常常被使用的,算法在整个STL中起到很关键的数据.本节介绍的是一些基本算法,包括equal.fill.fill_n,iter_swap.lexicographical_comp ...

  2. 菜鸟nginx源代码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)

    菜鸟nginx源代码剖析数据结构篇(六) 哈希表 ngx_hash_t(上) Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog. ...

  3. 菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)

      菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)   Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:B ...

  4. 【Redis源代码剖析】 - Redis内置数据结构之压缩字典zipmap

    原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51111230 今天为大家带来Redis中zipmap数据结构的分析,该结构定义在 ...

  5. 转】从源代码剖析Mahout推荐引擎

    原博文出自于: http://blog.fens.me/mahout-recommend-engine/ 感谢! 从源代码剖析Mahout推荐引擎 Hadoop家族系列文章,主要介绍Hadoop家族产 ...

  6. NGINX源代码剖析 之 CPU绑定(CPU亲和性)

    作者:邹祁峰 邮箱:Qifeng.zou.job@gmail.com 博客:http://blog.csdn.net/qifengzou 日期:2014.06.12 18:44 转载请注明来自&quo ...

  7. Qt中事件分发源代码剖析(一共8个步骤,顺序非常清楚:全局的事件过滤器,再传递给目标对象的事件过滤器,最终传递给目标对象)

    Qt中事件分发源代码剖析 Qt中事件传递顺序: 在一个应该程序中,会进入一个事件循环,接受系统产生的事件,并且进行分发,这些都是在exec中进行的.下面举例说明: 1)首先看看下面一段示例代码: in ...

  8. STL源代码剖析 读书总结

    <<STL源代码剖析>> 侯捷著 非常早就买了这本书, 一直没看, 如今在实验室师兄代码的时候发现里面使用了大量泛型编程的内容, 让我有了先看看这本书的想法. 看之前我对于泛型 ...

  9. 菜鸟nginx源代码剖析数据结构篇(一)动态数组ngx_array_t

    菜鸟nginx源代码剖析数据结构篇(一)动态数组ngx_array_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...

随机推荐

  1. Leetcode: Median of Two Sorted Arrays. java.

    There are two sorted arrays A and B of size m and n respectively. Find the median of the two sorted ...

  2. 改动导航栏上返回button上的字,比如把back改动为返回

    改动导航栏上返回button上的字,比如把back改动为返回 注意:这个须要在跳转之前到视图控制器中写,而不是在跳转之后到控制器中写 UIBarButtonItem *backIetm = [[UIB ...

  3. <转载>使CSS文字图片div元素居中方法之水平居中的几个方法

    文字居中,文字垂直居中水平居中,图片居中,图片水平居中垂直居中,块元素垂直居中?当我们在做前端开发是时候关于css居中的问题是很常见的.情 况有很多种,不同的情况又有不同的解决方式.水平居中的方式解决 ...

  4. C++基础学习笔记----第四课(函数的重载、C和C++的相互调用)

    本节主要讲了函数重载的主要概念以及使用方法,还有C和C++的相互调用的准则和具体的工程中的使用技巧. 函数重载 1.基本概念 函数重载就是用同一个函数名来定义不同的函数.使用不同的函数参数来搭配同一个 ...

  5. 简单理清一下proto与prototype

    这篇博客主要是为了理清自己的思路. 先上图,所有内容都从这张图来讲. 在js中,所有的东西都是对象,包括是function. prototype这个属性是函数特有的.有两层含义,第一层含义指的是某对象 ...

  6. Writing a Windows Shell Extension(marco cantu的博客)

    Writing a Windows Shell Extension This is a technical article covering the content of my last week s ...

  7. 配置VS2008下的Qt开发环境有感

    写一篇小小的日志为了在VS2008中安装Qt的插件,花了我很多的时间.1.vs2008在win7中破解问题我的VS2008已经安装好了,不知道为何,当初没有破解,现在只剩下15天限制了.于是为了破解, ...

  8. Servlet的学习之Request请求对象(2)

    在上一篇<Servlet的学习(十)>中介绍了HttpServletRequest请求对象的一些常用方法,而从这篇起开始介绍和学习HttpServletRequest的常用功能. 使用Ht ...

  9. WM_PAINT与WM_ERASEBKGND(用户操作和API这两种情况产生消息的顺序有所不同)

    1)当WM_PAINT不是由InvalidateRect产生时,即由最大化,最小化等产生时,或者移动产生(移动有时只会产生WM_ERASEBKGND消息)系统先发送WM_ERASEBKGND消息,再发 ...

  10. 微信JSAPI支付(比较详细) 关于getRrandWCPayRequest:fail_invalid appid 错误

    原文:微信JSAPI支付(比较详细) 关于getRrandWCPayRequest:fail_invalid appid 错误 首先微信支付需注册  微信公从平台开发 和 微信支付商户平台 关于4个密 ...