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. [HG]walk 题解

    前言 学长博客划水,抄题解,差评. 于是我来重新写一篇正常的题解,虽然解法跟标程不一样,但是复杂度是一样的. 题面 题目描述 在比特镇一共有\(n\)个街区,编号依次为\(1\)到\(n\),它们之间 ...

  2. github 的 fork 取消功能

    进入该 fork 目录后 找到 Settings 点击后拉到底 找到含有 Delete 字样的按钮点击 弹出的对话框输入你删除的这个项目名 后删除 链接

  3. (23)C++/Python项目练习一

    逆转字符串——输入一个字符串,将其逆转并输出. Python: def rev(s): return (s[::-1]) s =input("请输入一个字符串:") a = rev ...

  4. Java的参数传递是值传递?

    引用传递和值传递的区别.(不先说定义的都是在耍流氓!) 按值调用(call by value) : 在参数传递过程中,形参和实参占用了两个完全不同的内存空间.形参所存储的内容是实参存储内容的一份拷贝. ...

  5. Flask中的路由配置

    在Flask中也同样有django中的路由配置只不过没有djngo那么严格主要的参数有一下六个记住常用的就可以了 1.endpoint   反向生成url地址标志,默认视图函数名 2.methods ...

  6. 快速入门分布式消息队列之 RabbitMQ(1)

    目录 目录 前言 简介 安装 RabbitMQ 基本对象概念 Message 消息 Producer 生产者 Consumer 消费者 Queue 队列 Exchange 交换机 Binding 绑定 ...

  7. 阶段1 语言基础+高级_1-3-Java语言高级_04-集合_08 Map集合_5_Entry键值对对象

  8. 大数据架构师必读的NoSQL建模技术

    大数据架构师必读的NoSQL建模技术 从数据建模的角度对NoSQL家族系统做了比较简单的比较,并简要介绍几种常见建模技术. 1.前言 为了适应大数据应用场景的要求,Hadoop以及NoSQL等与传统企 ...

  9. 自动化测试中,元素无法点击定位等问题的解决:js的使用方法

    在自动化测试中经常会遇到使用selenium方法定位元素点击操作失败的情况,例如,我们想实现在浏览器输入http://www.baidu.com,进入百度首页后,鼠标悬停在“更多产品”上,点击“全部产 ...

  10. 20191105 《Spring5高级编程》笔记-第9章

    第9章 事务管理 一些名词: 2PC(2 Phase Commit) XA协议 JTS(Java Transaction Service) JCA(Java EE Connector Architec ...