Java多线程同步工具类:Semaphore原理剖析

news/2024/11/30 1:54:25/

Java多线程同步工具类:Semaphore原理剖析

文章目录

  • Java多线程同步工具类:Semaphore原理剖析
    • Semaphore原理
    • 实战案例

前驱知识准备:AbstractQueuedSynchronizer队列同步器

[Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析]

Semaphore原理

Semaphore也就是信号量,提供了资源数量的并发访问控制,可以用于限制访问某些资源(物理或逻辑的)的线程数目。Semaphore是AQS队列同步器中对共享锁的子类实现。Semaphore维护了一个许可证集合,有多少资源需要限制就维护多少许可证集合,假如这里有N个资源,那就对应于N个许可证,同一时刻也只能有N个线程访问。一个线程获取许可证就调用acquire方法,用完了释放资源就调用release方法。

Semaphore核心代码功能使用如下所示:

// ⼀开始有5份共享资源。第⼆个参数表示是否是公平
Semaphore myResources = new Semaphore(5, true);// 工作线程每获取⼀份资源,就在该对象上记下来
// 在获取的时候是按照公平的方式还是非公平的方式,就要看上⼀行代码的第二个参数了。
// ⼀般⾮公平抢占效率较高。
myResources.acquire();// 工作线程每归还⼀份资源,就在该对象上记下来
// 此时资源可以被其他线程使⽤
myResources.release();/*
释放指定数目的许可,并将它们归还给信标。
可用许可数加上该指定数目。
如果线程需要获取N个许可,在有N个许可可用之前,该线程阻塞。
如果线程获取了N个许可,还有可用的许可,则依次将这些许可赋予等待获取许可的其他线程。
*/
semaphore.release(2);/*
从信标获取指定数⽬的许可。如果可用许可数目不够,则线程阻塞,直到被中断。该⽅法效果与循环相同,
for (int i = 0; i < permits; i++) acquire();
只不过该方法是原⼦操作。如果可用许可数不够,则当前线程阻塞,直到:(⼆选⼀)
1. 如果其他线程释放了许可,并且可用的许可数满足当前线程的请求数字;
2. 其他线程中断了当前线程。permits – 要获取的许可数
*/
semaphore.acquire(3);

Semaphore在争抢资源时的示意图如下图所示,假设有n个线程来获取Semaphore⾥⾯的10份资源(n > 10), n个线程中只有10个线程能获取到,其他线程都会阻塞。直到有线程释放了资源,其他线程才能获取到。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7ePxdyV-1670158909096)(E:\笔记\截图\SemaPhore争抢资源示意图.png)]

当初始的资源个数为1的时候, Semaphore退化为排他锁。正因为如此, Semaphone的实现原理和锁十分类似,是基于AQS,有公平和非公平之分

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

SemaPhore也是使用了队列同步器AbstractQueuedSynchronizer来实现多线程间的同步操作,abstract static class Sync extends AbstractQueuedSynchronizer

acquire()方法通过判断剩余资源数和线程所需资源数的差值是否小于0,如果小于0,则当前线程进行阻塞,如果大于0,对state变量(state变量在AQS中表示同步状态)进行CAS减操作,减到0之后,线程阻塞。在release里对state变量进行CAS加操作。

public class Semaphore {protected final boolean tryReleaseShared(int releases) {for (;;) {int current = getState();int next = current + releases;if (next < current) // overflowthrow new Error("Maximum permit count exceeded");if (compareAndSetState(current, next))return true;}}static final class FairSync extends Sync {// ...FairSync(int permits) {super(permits);}protected int tryAcquireShared(int acquires) {for (;;) {if (hasQueuedPredecessors())return -1;int available = getState();// 判断剩余资源数,如果<0,说明资源数不够了,获取资源失败int remaining = available - acquires;if (remaining < 0 || compareAndSetState(available, remaining))return remaining;}}}
}/*** Acquires in shared interruptible mode.* @param arg the acquire argument*/private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {// addWaiter将当前线程封装成共享节点,放在等待队列尾部final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {// 当前线程前驱节点final Node p = node.predecessor();if (p == head) {// 尝试获取args数量的资源数,如果资源数不够,则返回<0int r = tryAcquireShared(arg);if (r >= 0) {// 向后传播唤醒当前节点后的节点setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

关于该方法的更多详细原理剖析,可以看AQS介绍的相关博客:[Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析](https://blog.csdn.net/Urbanears/article/details/128177063?spm=1001.2014.3001.5502)

实战案例

下面给出一个案例来测试Semaphore的使用:

case: 自习室抢座,写作业:

抢座位的线程:

import java.util.Random;
import java.util.concurrent.Semaphore;public class MyThread extends Thread {private final Semaphore semaphore;private final Random random = new Random();public MyThread(String name, Semaphore semaphore) {super(name);this.semaphore = semaphore;}@Overridepublic void run() {try {semaphore.acquire();System.out.println(Thread.currentThread().getName() + " - 抢座成功,开始写作业");Thread.sleep(random.nextInt(1000));System.out.println(Thread.currentThread().getName() + " - 作业完成,腾出座位");} catch (InterruptedException e) {e.printStackTrace();}semaphore.release();}
}

主方法:

import java.util.concurrent.Semaphore;
public class Demo {public static void main(String[] args) throws InterruptedException {Semaphore semaphore = new Semaphore(2);for (int i = 0; i < 5; i++) {new MyThread("学⽣-" + (i + 1), semaphore).start();}}
}

上面主方法中,调用new Semaphore(2)定义了2份共享资源,或者两份许可证,也就是同一时刻最多只允许2个线程执行。输出结果:

学⽣-1 - 抢座成功,开始写作业
学⽣-2 - 抢座成功,开始写作业
学⽣-2 - 作业完成,腾出座位
学⽣-3 - 抢座成功,开始写作业
学⽣-1 - 作业完成,腾出座位
学⽣-4 - 抢座成功,开始写作业
学⽣-3 - 作业完成,腾出座位
学⽣-5 - 抢座成功,开始写作业
学⽣-5 - 作业完成,腾出座位
学⽣-4 - 作业完成,腾出座位

出结果:

学⽣-1 - 抢座成功,开始写作业
学⽣-2 - 抢座成功,开始写作业
学⽣-2 - 作业完成,腾出座位
学⽣-3 - 抢座成功,开始写作业
学⽣-1 - 作业完成,腾出座位
学⽣-4 - 抢座成功,开始写作业
学⽣-3 - 作业完成,腾出座位
学⽣-5 - 抢座成功,开始写作业
学⽣-5 - 作业完成,腾出座位
学⽣-4 - 作业完成,腾出座位

http://www.ppmy.cn/news/1537.html

相关文章

数据结构栈的实现

目录栈的概念栈的结构声明初始化数据入栈出栈判断栈是否为空取栈顶的值销毁栈栈的概念 栈是一种线性表&#xff0c;插入数据的一端叫栈顶&#xff0c;另一端叫栈底。 入栈&#xff1a;数据从栈顶进入栈中 出栈&#xff1a;数据从栈顶删除 所以&#xff0c;栈的特点就是先进后出…

IDEA创建Java Web项目

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

微机-------CPU与外设之间的数据传送方式

目录 一、无条件方式二、查询方式三、中断方式四、DMA方式一、无条件方式 外设要求:简单、数据变化缓慢。 外设被认为始终处于就绪状态。始终准备好数据或者始终准备好接收数据。 IN AL,数据端口 数据端口的地址通过CPU的地址总线送到地址译码器进行译码,同时该指令进行的是…

内存优化之重新认识内存

我们知道&#xff0c;手机的内存是有限的&#xff0c;如果应用内存占用过大&#xff0c;轻则引起卡顿&#xff0c;重则导致应用崩溃或被系统强制杀掉&#xff0c;更严重的情况下会影响应用的留存率。因此&#xff0c;内存优化是性能优化中非常重要的一部分。但是&#xff0c;很…

Clickhouse 使用DBeaver连接

ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。 据处理大致可以分成两大类&#xff1a;联机事务处理OLTP&#xff08;on-line transaction processing&#xff09;、联机分析处理OLAP&#xff08;On-Line Analytical Processing&#xff09;。 OLTP是传统的…

如何选择和使用腾讯云服务器的方法新手教程

本文将介绍如何选择和使用腾讯云服务器的方法新手教程。云服务器能帮助快速构建更稳定、安全的应用&#xff0c;降低开发运维的难度和整体IT成本。腾讯云CVM云服务器提供多种类型的实例、操作系统和软件包。各实例中的 CPU、内存、硬盘和带宽可以灵活调整&#xff0c;以满足应用…

自制肥鲨HDO2电源升压延长线

自制肥鲨HDO2电源升压延长线1. 问题源由2. 解决方案3. 材料准备4. 最终延长线产出4.1 裸照4.2 成品5. 参考资料1. 问题源由 之前我们介绍了【自制肥鲨HDO2电源降压延长线&#xff0c;支持3S~6S动力电池】&#xff0c;主要解决使用动力电池给眼镜供电的问题。 但是马上有兄弟反…

面试整理一

** 面试整理 ** 借鉴链接&#xff1a;https://www.cnblogs.com/xiamaojjie/p/11891396.html https://blog.csdn.net/m0_58395003/article/details/117232056 https://zhuanlan.zhihu.com/p/58370623 框架分层&#xff1a;自动化测试框架分为5层&#xff08;配置层&#xff0c…