Semaphore 原理分析

分析下SemaPhore吧,也是基于AQS实现的,对并发进行控制的工具类,看下其怎么实现的,

    Semaphore semaphore = new Semaphore(3);semaphore.acquire();semaphore.release();

Semaphore 常用于控制并发量,比如这里设置为3,就可以只有三个线程可以acquire拿到资源,后续来的线程需要排队,等原有线程release释放之后,才可以接入新的请求,用于控制最大并发。

acquire 实现

// 默认非公平的
public Semaphore(int permits) {sync = new NonfairSync(permits);
}
public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 如果获取不到,走的下面阻塞进行入等待队列if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}
// 执行的AQS的获取资源
private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {// 添加共享节点final Node node = addWaiter(Node.SHARED);try {for (;;) {// 死循环判断,park之后唤醒,还是走这里final Node p = node.predecessor();// 如果前面是头节点的话if (p == head) {// 执行的子类实现的尝试方法int r = tryAcquireShared(arg);// 获取成功的话if (r >= 0) {// 对其进行唤醒setHeadAndPropagate(node, r);p.next = null; // help GCreturn;}}// 如果不是头节点,判断需要park不,前节点是signal就进行park// park之前检查是不是被打断// 如果第一次不是,会给前节点设置signal,然后下一次再循环到,就park了if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} catch (Throwable t) {cancelAcquire(node);throw t;}
}// 实际获取到锁之后,改头,然后传播,这里是不是传播根据子类返回的是0还是大于0private void setHeadAndPropagate(Node node, int propagate) {Node h = head; // Record old head for check belowsetHead(node);// 大于0,头节点为空(执行完了),状态小于0,// 新的头节点(当前节点)为空,或者状态小于0if (propagate > 0 || h == null || h.waitStatus < 0 ||(h = head) == null || h.waitStatus < 0) {Node s = node.next;//如果有后节点为空或者是共享的,释放if (s == null || s.isShared())doReleaseShared();}}private void doReleaseShared() {for (;;) {Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;// 这里会先把状态改为0,改成功了会是释放,成功释放之后if (ws == Node.SIGNAL) {if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))continue;            // loop to recheck casesunparkSuccessor(h);}// 如果为0 改为传播else if (ws == 0 &&!h.compareAndSetWaitStatus(0, Node.PROPAGATE))continue;                // loop on failed CAS}// 判断等于头,就是没改变头就breakif (h == head)                   // loop if head changedbreak;}}

可以看到这是在获取资源,获取不到的时候进入队列等待,默认的是非公平的,去看下怎么实现的

Sync

Semaphore 内部类Sync实现了AQS,看下怎么实现的

abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 1192457210091910933L;// 初始设置的资源数,也是通过stateSync(int permits) {setState(permits);}final int getPermits() {return getState();}// 非公平的获取资源final int nonfairTryAcquireShared(int acquires) {for (;;) {// 获取可用的资源int available = getState();// 如果可用的小于需要获取的int remaining = available - acquires;// 小于0直接返回了,如果不小于0,就cas设置,设置成功就返回对应的值了大于等于0的if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}// 释放资源protected final boolean tryReleaseShared(int releases) {for (;;) {int current = getState();// 给对应的数量加上释放的数量int next = current + releases;// 释放的不能为负数,也不能超过限制if (next < current) // overflowthrow new Error("Maximum permit count exceeded");// cas设置,成功返回释放完成if (compareAndSetState(current, next))return true;}}// 减去对应的statefinal void reducePermits(int reductions) {for (;;) {int current = getState();int next = current - reductions;if (next > current) // underflowthrow new Error("Permit count underflow");if (compareAndSetState(current, next))return;}}final int drainPermits() {for (;;) {// 是不是为0,不为0的时候尝试设置为0int current = getState();if (current == 0 || compareAndSetState(current, 0))return current;}}
}
// 看下对应的公平锁实现,非公平直接使用Sync的方法获取
static final class FairSync extends Sync {private static final long serialVersionUID = 2014338818796000944L;FairSync(int permits) {super(permits);}protected int tryAcquireShared(int acquires) {for (;;) {// 是不是有在等待的,有就返回-1了,差的就是这个判断if (hasQueuedPredecessors())return -1;int available = getState();int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}
}

可以看到Sync继承AQS之后实现的获取资源方法就是对对应的state进行减,确保其大于等于0,有就可以获取,公平非公平的实现就是判断喜爱是不是有在等待的,有的话直接返回-1,不进行尝试。

release

public void release() {sync.releaseShared(1);
}public final boolean releaseShared(int arg) {// 先改,成功就实际释放if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;
}
protected final boolean tryReleaseShared(int releases) {for (;;) {// 改了state的值int current = getState();int next = current + releases;if (next < current) // overflowthrow new Error("Maximum permit count exceeded");if (compareAndSetState(current, next))return true;}
}
private void doReleaseShared() {for (;;) {Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;// 唤醒后面if (ws == Node.SIGNAL) {if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))continue;            // loop to recheck cases// 实际唤醒线程unparkSuccessor(h);}else if (ws == 0 &&!h.compareAndSetWaitStatus(0, Node.PROPAGATE))continue;                // loop on failed CAS}if (h == head)                   // loop if head changedbreak;}
}// 唤醒线程
private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)node.compareAndSetWaitStatus(ws, 0);// 获取下一个节点,不为空的时候直接唤醒Node s = node.next;// 如果是空或者取消状态的话if (s == null || s.waitStatus > 0) {s = null;// 从后向前遍历,然后唤醒,这里唤醒之后应该去继续拿资源for (Node p = tail; p != node && p != null; p = p.prev)if (p.waitStatus <= 0)s = p;}if (s != null)LockSupport.unpark(s.thread);
}

总结

简单总结下吧,Semaphore 通过AQS的state来控制并发数量,也分为公平和非公平,但是使用的是共享锁,这样就能根据数量进行唤醒,AQS提供的方法tryAcquire 让子类实现的,返回正数代表可以继续向后唤醒,返回0自己得到资源可以执行,就通过这样的形式来控制并发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/36598.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

请教电路高手帮忙Review一下是否可行?

想要实现STM32 3.3V GPIO 控制5V电源通断&#xff0c;默认状态为&#xff1a;接通。 使用如下电路图有无问题&#xff1f;参数是否需要调整&#xff1f;

8.14 ARM

1.练习一 .text 文本段 .global _start 声明一个_start函数入口 _start: _start标签&#xff0c;相当于C语言中函数mov r0,#0x2mov r1,#0x3cmp r0,r1beq stopsubhi r0,r0,r1subcc r1,r1,r0stop: stop标签&#xff0c;相当于C语言中函数b stop 跳转到stop标签下的第一条…

C++的IO流

C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取数据&#xff0c;并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。注意宽度输出和精度输出控制。C语言借助了相应的缓冲区来…

javaScript:如何获取html中的元素对象

目录 前言&#xff1a; 方法 1.通过id获取元素 2.通过标签名获取元素 3.通过类名class获取元素 获取body的方法 1.document.getElementsByTagName(body)[0] 2.document.body 相关代码 前言&#xff1a; 通过获取HTML中的元素对象&#xff0c;JavaScript可以对网页进行动…

学生成绩管理系统V1.0

某班有最多不超过30人&#xff08;具体人数由键盘输入&#xff09;参加某门课程的考试&#xff0c;用一维数组作函数参数编程实现如下学生成绩管理&#xff1a; &#xff08;1&#xff09;录入每个学生的学号和考试成绩&#xff1b; &#xff08;2&#xff09;计算课程的总分…

Vue [Day7]

文章目录 自定义创建项目ESlint 代码规范vuex 概述创建仓库向仓库提供数据使用仓库中的数据通过store直接访问通过辅助函数 mapState&#xff08;简化&#xff09;mutations传参语法(同步实时输入&#xff0c;实时更新辅助函数 mapMutationsaction &#xff08;异步辅助函数map…

IntelliJ IDEA 2021/2022关闭双击shift全局搜索

我这里演示的是修改&#xff0c;删除是右键的时候选择Remove就好了 IDEA左上角 File-->Settings 找到Navigate -->Search Everywhere &#xff0c;右键添加快捷键。 OK --> Apply应用

C语言学习之const关键字的使用

const修饰变量&#xff1a;const关键字修饰变量时&#xff0c;该变量表示是一个只读变量&#xff0c;不能通过变量名修改变量的值&#xff1b;案例&#xff1a; const int m 20; int const n 30; m 30;//不可以赋值&#xff0c;因为m是只读变量 n 20;//不可以赋值&#xf…

初始多线程

目录 认识线程 线程是什么&#xff1a; 线程与进程的区别 Java中的线程和操作系统线程的关系 创建线程 继承Thread类 实现Runnable接口 其他变形 Thread类及其常见方法 Thread的常见构造方法 Thread类的几个常见属性 Thread类常用的方法 启动一个线程-start() 中断…

前端食堂技术周刊第 93 期:7 月登陆 Web 平台的新功能、Node.js 工具箱、Nuxt3 开发技巧、MF 重构方案

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;橙橙冰萃美式 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来…

Android多屏幕支持-Android12

Android多屏幕支持-Android12 1、概览及相关文章2、屏幕窗口配置2.1 配置xml文件2.2 DisplayInfo#uniqueId 屏幕标识2.3 adb查看信息 3、配置文件解析3.1 xml字段读取3.2 简要时序图 4、每屏幕焦点 android12-release 1、概览及相关文章 AOSP > 文档 > 心主题 > 多屏…

如何利用DeepBook自动做市商(AMM),发挥应用的最大价值

尽管Sui宣布DeepBook作为其首个本地流动性层&#xff0c;即中央限价单簿&#xff08;Central Limit Order Book&#xff0c;CLOB&#xff09;&#xff0c;但自动做市商&#xff08;Automated Market Maker&#xff0c;AMM&#xff09;平台也可以在Sui上发挥作用。事实上&#x…

理解jvm之对象已死怎么判断?

目录 引用计数算法 什么是引用 可达性分析算法&#xff08;用的最多的&#xff09; 引用计数算法 定义&#xff1a;在对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一&#xff1…

文件批量改名高手:轻松删除文件名,仅保留编号!

您是否经常需要对大量文件进行命名调整&#xff1f;是否为繁琐的手动操作而感到厌烦&#xff1f;现在&#xff0c;我们的智能批量文件改名工具为您提供了一种简单而高效的解决方案&#xff01;只需几步操作&#xff0c;您就能轻松删除原有的文件名&#xff0c;仅保留编号&#…

YOLO相关原理(文件结构、视频检测等)

超参数进化(hyperparameter evolution) 超参数进化是一种使用了genetic algorithm&#xff08;GA&#xff09;遗传算法进行超参数优化的一种方法。 YOLOv5的文件结构 images文件夹内的文件和labels中的文件存在一一对应关系 激活函数&#xff1a;非线性处理单元 activation f…

c#学习路线

文章目录 .net coreN层架构大项目实战高性能互联网项目架构c#高级编程各种主流框架分布式通信SSO单点登录+权限管理系统实战N层架构WEB安全ASP.NET MVCNoSQLORM框架c#6和c#7新语法VS插件分享项目管理三层项目实战三层架构ASP.NET基础数据库和ASP.NETADO.NET计算机基础计算机硬件…

C# 11 中的新增功能

本文内容 泛型属性泛型数学支持数值 IntPtr 和 UIntPtr字符串内插中的换行符 显示另外 11 个 C# 11 中增加了以下功能&#xff1a; 原始字符串字面量泛型数学支持泛型属性UTF-8 字符串字面量字符串内插表达式中的换行符列表模式文件本地类型必需的成员自动默认结构常量 str…

【设计模式】MVC 模式

MVC 模式代表 Model-View-Controller&#xff08;模型-视图-控制器&#xff09; 模式。这种模式用于应用程序的分层开发。 Model&#xff08;模型&#xff09; - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑&#xff0c;在数据变化时更新控制器。View&#xff…

Linux6.37 Kubernetes 集群调度

文章目录 计算机系统5G云计算第三章 LINUX Kubernetes 集群调度一、调度约束1.调度过程2.指定调度节点3.亲和性1&#xff09;节点亲和性2&#xff09;Pod 亲和性3&#xff09;键值运算关系 4.污点(Taint) 和 容忍(Tolerations)1&#xff09;污点(Taint)2&#xff09;容忍(Toler…

centos搭建k8s

centos搭建k8s环境_centos k8s_进击的Coders的博客-CSDN博客