简介

  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. 遭遇:“传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确” 错误

    http://www.cnblogs.com/delphinet/archive/2010/03/09/1681777.html 正在写一个类似文章的发表系统.其中记录文章内容的字段Contents设 ...

  2. adb相关基础知识集锦

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

  3. python学习(28) 浅谈可变对象的单例模式设计

    python开发,有时候需要设计单例模式保证操作的唯一性和安全性.理论上python语言底层实现和C/C++不同,python采取的是引用模式,当一个对象是可变对象,对其修改不会更改引用的指向,当一个 ...

  4. 使用tushare的pandas进行to_sql操作时的No module named 'MySQLdb'错误处理

    先写在前面,用tushare获取财经类数据时,完全没有必要用python3版本 py2功能没差别,但是py3有很多地方需要修改参数才能成功运行,无端造成时间的浪费 下面进入正题,这个问题困扰了我一个下 ...

  5. Oracle笔记 - unfinished

    1. plsql查看xmltype字段的xml格式时,出现中文乱码问题,可通过该字段.getClobVal():查询到的xml将是中文不乱码的. 2. extract函数查询xml某节点下的所有节点, ...

  6. PHP中GET和POST区别

    1. get是从服务器上获取数据,post是向服务器传送数据.2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到.post是通过H ...

  7. CTSC/APIO2018滚粗记

    CTSC/APIO2018滚粗记 前言 从\(5.5\)晚上的火车到\(5.14\)早上的高铁 \(10\)天的时间真的过去的很快. 眨眼间,就到了今天晚上的颁奖. 至于结果如何,反而并不是那么重要了 ...

  8. PB程序调用C++ COM生成对象发回-2问题

    C++写的COM组件用于读CPU卡,在C#中正常能够引用使用,但是在PB中却是返回-2,不识别类名,代码如下: OleObject ole_AddComole_AddCom = Create OLEO ...

  9. 产品排序(2015 年北大自招夏令营) (与栈相关的区间DP)

    题面: \(solution:\) 又是一道\(DP\)的好题啊!状态并不明显,需要仔细分析,而且还结合了栈的特性! 做这一类题,只要出题人有点理想,一定会在栈的性质上做点文章,所以我们尽量围绕栈的性 ...

  10. [转]html网页 swf播放器使用代码

    <object id="player" height="240" width="275" classid="CLSID:6B ...