前言

Semaphore也是JUC包中一个用于并发控制的工具类,举个常用场景的例子:有三台电脑五个人,每个人都要用电脑注册一个自己的账户,这时最开始只能同时有三个人操作电脑注册账户,这三个人中有人操作完了剩下的两个人才能占用电脑注册自己的账户。这就是Semaphore的经典使用场景,跟并发加锁有点像,只是我们的并发加锁同一时间只让有一个线程执行,而Semaphore的加锁控制是允许同一时间有指定数量的线程同时执行,超过这个数量就加锁控制。

一、使用样例

 public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 对比上面例子中的3台电脑
for (int i = 0; i < 5; i++) { // 对比上面例子中的5个人
new Thread(() -> {
try {
semaphore.acquire(1); // 注意acquire中的值可以传任意值>=0的整数
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " acquire 1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "release 1");
semaphore.release(1);
}).start();
}
}

执行结果为:

 Thread-0 acquire 1
Thread-2 acquire 1
Thread-1 acquire 1
Thread-1release 1
Thread-2release 1
Thread-0release 1
Thread-4 acquire 1
Thread-3 acquire 1
Thread-4release 1
Thread-3release 1

可以看到同一时间只有三个线程获取到了锁,这三个执行完释放了之后,剩下两个菜获取锁执行。下面看看源码是如何实现的。

二、源码实现

1、Semaphore构造器

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

可以看到,Semaphore有两个构造器,一个是只传数值默认非公平锁,另一个可指定用公平锁还是非公平锁。permits最终还是赋值给了AQS中的state变量。

2、acquire(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();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

1)、查看tryAcquireShared的实现方法

先看非公平锁的获取:

 final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires; // 如果remaining是负的,说明当前剩余的信号量不够了,需要阻塞
if (remaining < 0 ||
compareAndSetState(available, remaining)) // 如果remaining<0则直接return,不会走CAS;如果大于0,说明信号量还够,可走CAS将信号量减掉,成功则返回大于0的remaining
return remaining;
}
}

再看公平锁的获取:

 protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors()) // 判断是不是在队首,不是的话直接返回-1
return -1;
int available = getState(); // 后面逻辑通非公平锁的获取逻辑
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}

可以看到,不管非公平锁和公平锁,加锁时都是先判断当前state够不够减的,如果减出负数返回获取锁失败,是正数才走CAS将原信号量扣掉,返回获取锁成功。加锁时一个减state的过程。

2)、doAcquireSharedInterruptibly

此方法还是AQS中的实现,逻辑重复,就不再说明了。

3、release(1)方法

 public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}

同样调用了AQS中的模板方法releaseShared:

 public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

其中tryReleaseShared的实现在Semaphore类的Sync中,如下所示:

 protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases; // 用当前state加上要释放的releases
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next)) // 用CAS将state加上
return true;
}
}

另一个方法doReleaseShared之前看过,此处就不赘述了。

三、小结

Semaphore信号量类基于AQS的共享锁实现,有公平锁和非公平锁两个版本。它的加锁与释放锁的不同之处在于和普通的加锁释放锁反着,ReentrantLock和ReentrantReadWriteLock中都是加锁时state+1,释放锁时state-1,而Semaphore中是加锁时state减,释放锁时state加。

另外,如果它还可以acquire(2) 、release(1),即获取的和释放的信号量可以不一致,只是需要注意别释放的信号量太少导致后续任务获取不到足够的量而永久阻塞。

AQS系列(六)- Semaphore的使用及原理的更多相关文章

  1. java多线程系列(六)---线程池原理及其使用

    线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...

  2. java基础解析系列(六)---深入注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...

  3. java基础解析系列(六)---注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...

  4. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  5. Java并发编程系列-(8) JMM和底层实现原理

    8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...

  6. AQS系列(七)- 终篇:AQS总结

    前言 本文是对之前AQS系列文章的一个小结,首先看看以下几个问题: 1.ReentrantLock和ReentrantReadWriteLock的可重入特性是如何实现的? 2.哪个变量控制着锁是否被占 ...

  7. CountDownLatch、CyclicBarrier和Semaphore 使用示例及原理

    备注:博客园的markDown格式支持的特别不友好.也欢迎查看我的csdn的此篇文章链接:CountDownLatch.CyclicBarrier和Semaphore 使用示例及原理 CountDow ...

  8. Bing Maps进阶系列六:使用Silverlight剪切(Clip)特性实现Bing Maps的迷你小地图

    Bing Maps进阶系列六:使用Silverlight剪切(Clip)特性实现Bing Maps的迷你小地图 Bing Maps Silverlight Control虽然为我们提供了简洁.方面的开 ...

  9. AQS系列(一)- ReentrantLock的加锁

    前言 AQS即AbstractQueuedSynchronizer,是JUC包中的一个核心抽象类,JUC包中的绝大多数功能都是直接或间接通过它来实现的.本文是AQS系列的第一篇,后面会持续更新多篇,争 ...

  10. 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念

    深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...

随机推荐

  1. Codecommit

    1. 生成IAM 用户组并附权限. 2.生成IAM用户并加入组. 3. 为用户生成key-pair 4. 上传公钥到aws 5. 配置config文件,其中user是aws 为公钥生成的id. 6. ...

  2. 使用node.js开发一个生成逐帧动画小工具

    在实际工作中我们已经下下来不下于一万个npm包了,像我们熟悉的 vue-cli,react-native-cli 等,只需要输入简单的命令 vue init webpack project,即可快速帮 ...

  3. Siamese-RPN论文阅读

    https://www.cnblogs.com/zhengyuqian/p/10609737.html

  4. Java从零开始(前篇)

    前篇 自述 本人大三通信专业,咸鱼一枚,对专业所学傅里叶变换等实在提不起兴趣. 幸好略学过c系列语言,但也浅尝辄止,浑浑噩噩,深入之后被指针弄地晕头转向. 想在毕业后转行计算机,于是我下定决心从零开始 ...

  5. [Odoo12基础教程]之第一篇-创建Todo应用

    声明: 本教程基于 Ruter 老师的 [Odoo基础教程系列] ,Ruter老师教程的链接地址为:Odoo基础教程系列   . 至于为什么已经有了Ruter老师的教程,还要自己再搬移一份呢?是基于一 ...

  6. 【NHOI2018】跳伞登山赛

    [题目描述] 某山区有高高低低的 n 个山峰,根据海拔高度的不同,这些山峰由低到高进行了 1 到 n 编号.有 m 条只能单向通行的羊肠小道连接这些山峰.现在,这里要举行一场跳伞登山赛,选手们伞降到某 ...

  7. JVM学习笔记(1)--运行时数据区域

    运行时数据区域 相对于c,c++.程序设计时,java并不需要手动释放或者创建内存用于存放程序,这的确使得java开发变得容易和轻松,一旦有一天出现了内存泄漏或者内存溢出的问题,如果不了解JVM虚拟机 ...

  8. vue路由跳转

  9. JS中的同步异步编程

    首先我们先看看同步与异步的定义,及浏览器的执行机制,方便我们更好地理解同步异步编程. 浏览器是多线程的,JS是单线程的(浏览器只分配一个线程来执行JS)   进程大线程小:一个进程中包含多个线程,例如 ...

  10. CentOS 7 Nginx部署.NET Core Web应用

    部署.NET Core运行时 必要前提 在安装.NET Core前,需要注册Microsoft签名秘钥并添加Microsoft产品提要,每台机器只需要注册一次,执行如下命令: sudo rpm -Uv ...