简介

  Semaphore[ˈseməfɔ:(r)]意为信号量,比较书面的解释是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

  Semaphore维护了信号量许可,线程只有获得了许可才能够访问资源,可以把Semaphore理解为风景区管理员,风景区有人数限制,达到了人数限制管理员就会让后来的游客等着直到风景区里面的游客离开,这里风景区相当于需要协调的公共资源,人数限制就相当于Semaphore维护的许可量,而游客就相当于是执行任务的线程。

数据结构

  Semaphore是基于共享锁实现的,内部类Sync是同步器AQS的子类,Sync有两个子类:公平信号量FairSync和非公平信号量NonefairSync,Semaphore默认非公平策略。

示例

public class SemaphoreTest {

    private static final int THREAD_COUNT = 10;
private static ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT); public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < THREAD_COUNT; i++) {
pool.execute(() -> {
try {
semaphore.acquire();
System.out.println("Thread " + Thread.currentThread().getId() + " is saving data");
Thread.sleep(1000);
System.out.println("Thread " + Thread.currentThread().getId() + " finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
} pool.shutdown();
}
}

运行结果:

结果表明:同时只有三个线程能执行,因为Semaphore许可只有3个,相当于只有三个现场能访问同步资源,只有当线程释放许可,其他线程才能获取许可访问同步资源。

实现分析

构造方法

  Semaphore提供两种构造函数,默认是非公平策略的,会根据传入的许可permits设置同步状态state。

public class Semaphore implements java.io.Serializable {
private static final long serialVersionUID = -3222578661600680210L;
/** All mechanics via AbstractQueuedSynchronizer subclass */
private final Sync sync; public Semaphore(int permits) {
sync = new NonfairSync(permits);
} public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
} abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
//根据permits设置AQSstate
Sync(int permits) {
setState(permits);
} final int getPermits() {
return getState();
}
}
}

信号量的获取(公平方式)

  Semaphore提供的获取许可permits方法有acquire方法,都是调用的Sync分类AQS的acquireSharedInterruptibly方法,首先介绍基于公平策略如何获取信号量的。

	//获取一个许可
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//获取多个许可
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
//AQS方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//有中断则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取“共享锁”;获取成功则直接返回,获取失败,则通过doAcquireSharedInterruptibly()获取。
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

tryAcquireShared(int acquires)

  Sync子类FairSync实现的tryAcquireShared方法,首先判断AQS同步队列还有没有其他正在等待的线程,如果当前线程前面没有等待线程,尝试CAS修改,采用的是循环+CAS的方式修改同步状态

    protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
//目前还有多少许可
int available = getState();
//当前线程获得acquires个许可后剩下的许可
int remaining = available - acquires;
//剩下的许可大于0,CAS修改
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

doAcquireSharedInterruptibly(int arg)

  如果尝试获取失败,就会调用AQS的doAcquireSharedInterruptibly方法,首先会以当前线程构成共享型Node节点加入同步队列尾部,如果上一个节点是head节点,就尝试获取共享锁,否则就进入等待状态,等待前继节点成为head节点释放共享锁并唤醒后继节点。

    private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 创建”当前线程“的Node节点,且Node中记录的锁是”共享锁“类型;并将该节点添加到AQS同步队列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//当前节点的上一个节点p
final Node p = node.predecessor();
//节点p是头节点就尝试修改同步状态
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 当前线程一直等待,直到获取到共享锁。
// 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

信号量的释放(公平方式)

  信号量的释放,本质上就是释放获取到的共享锁。与acquire方法对应,释放信号量也有两种release方法,都调用了AQS的releaseShared方法。

	//释放一个许可
public void release() {
sync.releaseShared(1);
}
//释放多个许可
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
} //AQS方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

tryReleaseShared(int releases)

  tryReleaseShared方法是有内部类Sync提供实现的,意味着公平方式与非公平方式释放共享锁的实现相同的。循环+CAS修改同步状态。

    protected final boolean tryReleaseShared(int releases) {
for (;;) {
//当前同步状态/许可数
int current = getState();
//释放了releases个许可后剩余的许可数
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
//CAS修改同步状态
if (compareAndSetState(current, next))
return true;
}
}

doReleaseShared()

  tryReleaseShared成功释放后,doReleaseShared唤醒等待线程

    private void doReleaseShared() {
for (;;) {
//头节点
Node h = head;
// 如果头节点不为null,并且头节点不等于tail节点。同步队列除了head节点还有其他等待节点
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

nonfairTryAcquireShared(int acquires)

  非公平方式获取以及释放信号量的实现与公平方式只有tryAcquireShared的实现不同,释放的逻辑是相同的。

    protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
} final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

总结

  基于共享锁实现的Semaphore可以控制一定数量的线程同时访问同步资源,超过数量的线程需要等待直到有线程完成操作释放许可,从而保证合理使用同步资源。

多线程学习笔记七之信号量Semaphore的更多相关文章

  1. Java IO学习笔记七:多路复用从单线程到多线程

    作者:Grey 原文地址:Java IO学习笔记七:多路复用从单线程到多线程 在前面提到的多路复用的服务端代码中, 我们在处理读数据的同时,也处理了写事件: public void readHandl ...

  2. java多线程学习笔记——详细

    一.线程类  1.新建状态(New):新创建了一个线程对象.        2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...

  3. (转)Qt Model/View 学习笔记 (七)——Delegate类

    Qt Model/View 学习笔记 (七) Delegate  类 概念 与MVC模式不同,model/view结构没有用于与用户交互的完全独立的组件.一般来讲, view负责把数据展示 给用户,也 ...

  4. Learning ROS for Robotics Programming Second Edition学习笔记(七) indigo PCL xtion pro live

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...

  5. JAVA多线程学习笔记(1)

    JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...

  6. Typescript 学习笔记七:泛型

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  7. 多线程学习笔记九之ThreadLocal

    目录 多线程学习笔记九之ThreadLocal 简介 类结构 源码分析 ThreadLocalMap set(T value) get() remove() 为什么ThreadLocalMap的键是W ...

  8. python3.4学习笔记(七) 学习网站博客推荐

    python3.4学习笔记(七) 学习网站博客推荐 深入 Python 3http://sebug.net/paper/books/dive-into-python3/<深入 Python 3& ...

  9. Go语言学习笔记七: 函数

    Go语言学习笔记七: 函数 Go语言有函数还有方法,神奇不.这有点像python了. 函数定义 func function_name( [parameter list] ) [return_types ...

随机推荐

  1. “Uncaught TypeError: string is not a function”

    http://www.cnblogs.com/haitao-fan/archive/2013/11/08/3414678.html 今天在js中写了一个方法叫做search(),然后点击按钮的时候提示 ...

  2. adb相关基础知识集锦

    Android单元测试adb shell am instrument -w adb shell dumpsys adb logcat介绍

  3. VBA:Double类型与Decimal类型

    Sub DataType() For i = 0 To 100 t1 = t1 + 0.1 t2 = t2 + CDec(0.1) Debug.Print "Double=" &a ...

  4. Python教你找到最心仪的对象

    规则 单身妹妹到了适婚年龄,要选对象.候选男子100名,都是单身妹妹没有见过的.百人以随机顺序,从单身妹妹面前逐一经过.每当一位男子在单身妹妹面前经过时,单身妹妹要么选他为配偶,要么不选.如果选他,其 ...

  5. DNA序列编码中Hairpin的定义和计算

    DNA序列编码中Hairpin的定义和计算 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 [1] 张凯. DNA计算核酸编码优化及算法设计[D]. 2008. [2] Shin, ...

  6. [转]Python numpy函数hstack() vstack() stack() dstack() vsplit() concatenate()

    Python numpy函数hstack() vstack() stack() dstack() vsplit() concatenate() 觉得有用的话,欢迎一起讨论相互学习~Follow Me ...

  7. weblogic11G 修改密码

    weblogic11的登录密码修改方法: 1. 登陆到weblogic后选中domain structure下的security Realms(如图一)   (图一) 详情如图二: (图二) 2. 双 ...

  8. 关于Html class id 命名规范

    CSS命名规则 头:header 内容:content/containe 尾:footer 导航:nav 侧栏:sidebar 栏目:column 页面外围控制整体布局宽度:wrapper 左右中:l ...

  9. LaTeX符号和图片

    \documentclass{article} \usepackage{ctex} %中文处理 \begin{document} \section{空白符号} Are you wiser than o ...

  10. Spring 学习02

    一.上节内容回顾 1 spring的概念 (1)核心:ioc和aop (2)spring一站式框架 2 spring的bean管理(xml) (1)bean实例化 (2)注入属性 (3)注入对象属性 ...