JUC第二十八讲:JUC工具类: Semaphore详解

JUC工具类: Semaphore详解

本文是JUC第二十八讲,JUC工具类: Semaphore详解。Semaphore底层是基于AbstractQueuedSynchronizer来实现的。Semaphore称为计数信号量,它允许n个任务同时访问某个资源,可以将信号量看做是在向外分发使用资源的许可证,只有成功获取许可证,才能使用资源

文章目录

  • JUC工具类: Semaphore详解
    • 1、带着BAT大厂的面试问题去理解
    • 2、Semaphore源码分析
      • 2.1、类的继承关系
      • 2.2、类的内部类
      • 2.3、类的内部类 - Sync类
      • 2.4、类的内部类 - NonfairSync类
      • 2.5、类的内部类 - FairSync类
      • 2.6、类的属性
      • 2.7、类的构造函数
      • 2.8、核心函数分析 - acquire函数
      • 2.9、核心函数分析 - release函数
    • 3、Semaphore示例
    • 4、更深入理解
      • 4.1、单独使用Semaphore是不会使用到AQS的条件队列的
      • 4.2、场景问题
        • 1、semaphore初始化有10个令牌,11个线程同时各调用1次acquire方法,会发生什么?
        • 2、semaphore初始化有10个令牌,一个线程重复调用11次acquire方法,会发生什么?
        • 3、semaphore初始化有1个令牌,1个线程调用一次acquire方法,然后调用两次release方法,之后另外一个线程调用acquire(2)方法,此线程能够获取到足够的令牌并继续运行吗?
        • 4、semaphore初始化有2个令牌,一个线程调用1次release方法,然后一次性获取3个令牌,会获取到吗?
    • 5、参考文章

1、带着BAT大厂的面试问题去理解

请带着这些问题继续后文,会很大程度上帮助你更好的理解相关知识点。

  • 什么是Semaphore? 计数信号量,允许n个任务同时访问某个资源
  • Semaphore内部原理?
  • Semaphore常用方法有哪些? 如何实现线程同步和互斥的?
  • Semaphore适合用在什么场景?
  • 单独使用Semaphore是不会使用到AQS的条件队列?
  • Semaphore中申请令牌(acquire)、释放令牌(release)的实现?
  • Semaphore初始化有10个令牌,11个线程同时各调用1次acquire方法,会发生什么?
  • Semaphore初始化有10个令牌,一个线程重复调用11次acquire方法,会发生什么?
  • Semaphore初始化有1个令牌,1个线程调用一次acquire方法,然后调用两次release方法,之后另外一个线程调用acquire(2)方法,此线程能够获取到足够的令牌并继续运行吗?
  • Semaphore初始化有2个令牌,一个线程调用1次release方法,然后一次性获取3个令牌,会获取到吗?

2、Semaphore源码分析

2.1、类的继承关系

public class Semaphore implements java.io.Serializable {}

说明: Semaphore实现了Serializable接口,即可以进行序列化。

2.2、类的内部类

Semaphore总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。

img

说明: Semaphore与ReentrantLock的内部类的结构相同,类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。下面逐个进行分析。

2.3、类的内部类 - Sync类

Sync类的源码如下

// 内部类,继承自AQS
abstract static class Sync extends AbstractQueuedSynchronizer {// 版本号private static final long serialVersionUID = 1192457210091910933L;// 构造函数Sync(int permits) {// 设置状态数setState(permits);}// 获取许可final int getPermits() {return getState();}// 共享模式下非公平策略获取final int nonfairTryAcquireShared(int acquires) {for (;;) { // 无限循环// 获取许可数int available = getState();// 剩余的许可int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining)) // 许可小于0或者比较并且设置状态成功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");if (compareAndSetState(current, next)) // 比较并进行设置成功return true;}}// 根据指定的缩减量减小可用许可的数目final 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 (;;) { // 无限循环// 获取许可int current = getState();if (current == 0 || compareAndSetState(current, 0)) // 许可为0或者比较并设置成功return current;}}
}

说明: Sync类的属性相对简单,只有一个版本号,Sync类存在如下方法和作用如下。

img

2.4、类的内部类 - NonfairSync类

NonfairSync类继承了Sync类,表示采用非公平策略获取资源,其只有一个tryAcquireShared方法,重写了AQS的该方法,其源码如下:

static final class NonfairSync extends Sync {// 版本号private static final long serialVersionUID = -2694183684443567898L;// 构造函数NonfairSync(int permits) {super(permits);}// 共享模式下获取protected int tryAcquireShared(int acquires) {return nonfairTryAcquireShared(acquires);}
}

说明: 从tryAcquireShared方法的源码可知,其会调用父类Sync的nonfairTryAcquireShared方法,表示按照非公平策略进行资源的获取。

2.5、类的内部类 - FairSync类

FairSync类继承了Sync类,表示采用公平策略获取资源,其只有一个tryAcquireShared方法,重写了AQS的该方法,其源码如下。

protected int tryAcquireShared(int acquires) {for (;;) { // 无限循环if (hasQueuedPredecessors()) // 同步队列中存在其他节点return -1;// 获取许可int available = getState();// 剩余的许可int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining)) // 剩余的许可小于0或者比较设置成功return remaining;}
}

说明: 从tryAcquireShared方法的源码可知,它使用公平策略来获取资源,它会判断同步队列中是否存在其他的等待节点。

2.6、类的属性

public class Semaphore implements java.io.Serializable {// 版本号private static final long serialVersionUID = -3222578661600680210L;// 属性private final Sync sync;
}

说明: Semaphore自身只有两个属性,最重要的是sync属性,基于Semaphore对象的操作绝大多数都转移到了对sync的操作。

2.7、类的构造函数

  • Semaphore(int)型构造函数
public Semaphore(int permits) {sync = new NonfairSync(permits);
}

说明: 该构造函数会创建具有给定的许可数和非公平的公平设置的Semaphore。

  • Semaphore(int, boolean)型构造函数
public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

说明: 该构造函数会创建具有给定的许可数和给定的公平设置的Semaphore。

2.8、核心函数分析 - acquire函数

此方法从信号量获取一个(多个)许可,在提供一个许可前一直将线程阻塞,或者线程被中断,其源码如下

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

说明: 该方法中将会调用Sync对象的acquireSharedInterruptibly(从AQS继承而来的方法)方法,而acquireSharedInterruptibly方法在上一篇CountDownLatch中已经进行了分析,在此不再累赘。

最终可以获取大致的方法调用序列(假设使用非公平策略)。如下图所示。

img

说明: 上图只是给出了大体会调用到的方法,和具体的示例可能会有些差别,之后会根据具体的示例进行分析。

2.9、核心函数分析 - release函数

此方法释放一个(多个)许可,将其返回给信号量,源码如下。

public void release() {sync.releaseShared(1);
}

说明: 该方法中将会调用Sync对象的releaseShared(从AQS继承而来的方法)方法,而releaseShared方法在上一篇CountDownLatch中已经进行了分析,在此不再累赘。

最终可以获取大致的方法调用序列(假设使用非公平策略)。如下图所示:

img

说明: 上图只是给出了大体会调用到的方法,和具体的示例可能会有些差别,之后会根据具体的示例进行分析。

3、Semaphore示例

下面给出了一个使用Semaphore的示例。

import java.util.concurrent.Semaphore;class MyThread extends Thread {private Semaphore semaphore;public MyThread(String name, Semaphore semaphore) {super(name);this.semaphore = semaphore;}public void run() {        int count = 3;System.out.println(Thread.currentThread().getName() + " trying to acquire");try {semaphore.acquire(count);System.out.println(Thread.currentThread().getName() + " acquire successfully");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(count);System.out.println(Thread.currentThread().getName() + " release successfully");}}
}public class SemaphoreDemo {public final static int SEM_SIZE = 10;public static void main(String[] args) {Semaphore semaphore = new Semaphore(SEM_SIZE);MyThread t1 = new MyThread("t1", semaphore);MyThread t2 = new MyThread("t2", semaphore);t1.start();t2.start();int permits = 5;System.out.println(Thread.currentThread().getName() + " trying to acquire");try {semaphore.acquire(permits);System.out.println(Thread.currentThread().getName() + " acquire successfully");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release();System.out.println(Thread.currentThread().getName() + " release successfully");}      }
}

运行结果(某一次):

main trying to acquire
main acquire successfully
t1 trying to acquire
t1 acquire successfully
t2 trying to acquire
t1 release successfully
main release successfully
t2 acquire successfully
t2 release successfully

说明: 首先,生成一个信号量,信号量有10个许可,然后,main,t1,t2三个线程获取许可运行,根据结果,可能存在如下的一种时序。

img

说明: 如上图所示,首先,main线程执行acquire操作,并且成功获得许可,之后t1线程执行acquire操作,成功获得许可,之后t2执行acquire操作,由于此时许可数量不够,t2线程将会阻塞,直到许可可用。之后t1线程释放许可,main线程释放许可,此时的许可数量可以满足t2线程的要求,所以,此时t2线程会成功获得许可运行,t2运行完成后释放许可。下面进行详细分析。

  • main线程执行semaphore.acquire操作。主要的函数调用如下图所示。

img

说明: 此时,可以看到只是AQS的state变为了5,main线程并没有被阻塞,可以继续运行。

  • t1线程执行semaphore.acquire操作。主要的函数调用如下图所示。

img

说明: 此时,可以看到只是AQS的state变为了2,t1线程并没有被阻塞,可以继续运行。

  • t2线程执行semaphore.acquire操作。主要的函数调用如下图所示。

img

说明: 此时,t2线程获取许可不会成功,之后会导致其被禁止运行,值得注意的是,AQS的state还是为2。

  • t1执行semaphore.release操作。主要的函数调用如下图所示。

img

说明: 此时,t2线程将会被unpark,并且AQS的state为5,t2获取cpu资源后可以继续运行。

  • main线程执行semaphore.release操作。主要的函数调用如下图所示。

img

说明: 此时,t2线程还会被unpark,但是不会产生影响,此时,只要t2线程获得CPU资源就可以运行了。此时,AQS的state为10。

  • t2获取CPU资源,继续运行,此时t2需要恢复现场,回到parkAndCheckInterrupt函数中,也是在should继续运行。主要的函数调用如下图所示。

img

说明: 此时,可以看到,Sync queue中只有一个结点,头节点与尾节点都指向该结点,在setHeadAndPropagate的函数中会设置头节点并且会unpark队列中的其他结点。

  • t2线程执行semaphore.release操作。主要的函数调用如下图所示。

img

说明: t2线程经过release后,此时信号量的许可又变为10个了,此时Sync queue中的结点还是没有变化。

4、更深入理解

4.1、单独使用Semaphore是不会使用到AQS的条件队列的

不同于CyclicBarrier和ReentrantLock,单独使用Semaphore是不会使用到AQS的条件队列的,其实,只有进行await操作才会进入条件队列,其他的都是在同步队列中,只是当前线程会被park。

4.2、场景问题

1、semaphore初始化有10个令牌,11个线程同时各调用1次acquire方法,会发生什么?

答案:拿不到令牌的线程阻塞,不会继续往下运行。

2、semaphore初始化有10个令牌,一个线程重复调用11次acquire方法,会发生什么?

答案:线程阻塞,不会继续往下运行。可能你会考虑类似于锁的重入的问题,很好,但是,令牌没有重入的概念。你只要调用一次acquire方法,就需要有一个令牌才能继续运行。

3、semaphore初始化有1个令牌,1个线程调用一次acquire方法,然后调用两次release方法,之后另外一个线程调用acquire(2)方法,此线程能够获取到足够的令牌并继续运行吗?

答案:能,原因是release方法会添加令牌,并不会以初始化的大小为准。

4、semaphore初始化有2个令牌,一个线程调用1次release方法,然后一次性获取3个令牌,会获取到吗?

答案:能,原因是release会添加令牌,并不会以初始化的大小为准。Semaphore中release方法的调用并没有限制要在acquire后调用。

具体示例如下,如果不相信的话,可以运行一下下面的demo,在做实验之前,笔者也认为应该是不允许的。。

public class TestSemaphore2 {public static void main(String[] args) {int permitsNum = 2;final Semaphore semaphore = new Semaphore(permitsNum);try {System.out.println("availablePermits:"+semaphore.availablePermits()+",semaphore.tryAcquire(3,1, TimeUnit.SECONDS):"+semaphore.tryAcquire(3,1, TimeUnit.SECONDS));semaphore.release();System.out.println("availablePermits:"+semaphore.availablePermits()+",semaphore.tryAcquire(3,1, TimeUnit.SECONDS):"+semaphore.tryAcquire(3,1, TimeUnit.SECONDS));}catch (Exception e) {}}
}

5、参考文章

  • 【JUC】JDK1.8源码分析之Semaphore
  • Semaphore原理浅析和相关面试题分析

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

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

相关文章

亳州市的自然风光与旅游资源:欣赏安徽省中部的壮丽景色

亳州市是中国安徽省的一个地级市&#xff0c;位于该省的中部。 亳州市辖区包括谯城区、涡阳县、蒙城县和利辛县等地。亳州市拥有悠久的历史和丰富的文化遗产&#xff0c;同时也以其独特的自然风光而闻名。 首先&#xff0c;让我们来了解一下亳州的历史和景点。亳州的历史可以…

软件‘小程序‘前台开发软件定制的知识|app网站搭建

软件&#xff0c;小程序&#xff0c;前台开发软件定制的知识 随着互联网的快速发展&#xff0c;软件&#xff0c;小程序&#xff0c;前台开发软件定制已经成为了企业必备的工具。它可以帮助企业更好地管理业务&#xff0c;提高效率&#xff0c;增强用户体验。那么&#xff0c;什…

vue3学习(五)--- 父子组件传值

文章目录 defineProps普通写法TS写法 defineEmits普通写法TS写法 defineExpose defineProps 和 defineEmits 都是只能在 <script setup> 中使用的编译器宏。他们不需要导入&#xff0c;且会随着 <script setup> 的处理过程一同被编译掉。 defineProps 接收父组件传…

MySQL中使用函数会使索引失效?

文章目录 1、前置准备2、ChatGPT的答案3、实践证明SQL1SQL2SQL3SQL4SQL5 4、总结 1、前置准备 首先创建我们要测试的库表 CREATE TABLE lianhe_index (id int(11) NOT NULL AUTO_INCREMENT COMMENT id,name varchar(255) DEFAULT NULL,age int(11) DEFAULT NULL,number int(1…

Flink报错could not be loaded due to a linkage failure

文章目录 1、报错2、原因3、解决 1、报错 在Flink上提交作业&#xff0c;点Submit没反应&#xff0c;F12看到接口报错信息为&#xff1a; 大概意思是&#xff0c;由于链接失败&#xff0c;无法加载程序的入口点类xx。没啥鸟用的信息&#xff0c;去日志目录继续分析&#xff1a…

Django Channels、WS协议及同步与异步详解

CONTENTS 1. 同步与异步2. WebSocket3. 在JavaScript中使用WebSocket4. Django Channels 1. 同步与异步 在 Django 中&#xff0c;同步和异步主要涉及到请求处理的方式。这两种方式的主要区别在于它们如何处理多个并发请求&#xff1a; 同步&#xff08;Synchronous&#xff…

删除的通话记录也能找回!如何iPhone很早以前的通话记录

是否搜索过忘记保存的拨号号码?是的!我们都经历过,这可能真的很烦人,尤其是当你的iPhone决定隐藏你的通话记录时。但别担心,在这篇文章中,我将向你展示如何使用各种方法查看一个月前iPhone上的通话历史记录。因此,让我们来了解和发现如何在iPhone上查看你过去的通话记录…

什么是指标体系,怎么搭建一套完整的指标体系?(附PDF素材)

什么是指标体系&#xff0c;怎么搭建一套完整的指标体系&#xff1f;数字化转型过程中&#xff0c;这个问题一直困扰着数据分析师。主要体现在&#xff1a; 各部门根据业务需求&#xff0c;都有一部分量化指标&#xff0c;但不够全面&#xff0c;对企业整体数据分析应用能力提…

算法练习12——跳跃游戏

LeetCode 55 跳跃游戏 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 贪…

Python日志模块dome

import os import logbook from logbook.more import ColorizedStderrHandler from functools import wrapscur_path os.path.dirname(os.path.dirname(__file__)) # 当前目录的上级目录 log_path os.path.join(cur_path, logs) # 文件夹拼接 if not os.path.exists(log_pa…

MySQL锁概述

数据库锁是一种机制&#xff0c;用于管理并发访问数据库的方式。当多个用户或事务同时访问数据库时&#xff0c;可能会导致数据不一致或冲突的问题。数据库锁的作用是确保数据的一致性和完整性&#xff0c;同时允许多个用户并发地访问数据库。 需要注意的是&#xff0c;加锁是消…

ajax同步与异步,json-serve的安装与使用,node.js的下载

20.ajax json 轻量级的数据格式做配置文件网络传输 xml 重量级的数据格式 可扩展标记语言做配置文件网络传输 现在目前主流就是大量采用json做网络传输数据格式 1.ajax的概念: 与服务器进行’通信’的一种技术,能够实现异步的刷新页面 **同步:**按照顺序一步步的执行,容易造…

VMware和Debian下载

文章目录 ⭐️写在前面的话⭐️一、VMware二、Debain三、建立虚拟机&#x1f680; 先看后赞&#xff0c;养成习惯&#xff01;&#x1f680;&#x1f680; 先看后赞&#xff0c;养成习惯&#xff01;&#x1f680; ⭐️写在前面的话⭐️ CSDN主页&#xff1a;程序员好冰 目前在…

多输入多输出 | MATLAB实现CNN-GRU-Attention卷积神经网络-门控循环单元结合SE注意力机制的多输入多输出预测

多输入多输出 | MATLAB实现CNN-GRU-Attention卷积神经网络-门控循环单元结合SE注意力机制的多输入多输出预测 目录 多输入多输出 | MATLAB实现CNN-GRU-Attention卷积神经网络-门控循环单元结合SE注意力机制的多输入多输出预测预测效果基本介绍程序设计往期精彩参考资料 预测效果…

使用PyTorch处理多维特征输入的完美指南

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

C++ 模板同时使用默认参数和偏特化

C 模板同时使用默认参数和偏特化 正常的偏特化都很简单&#xff0c;但是如果和默认参数碰到一起就会复杂一点点 先讲答案&#xff08;按照顺序&#xff09;&#xff1a; 如果显示指定了&#xff0c;首先看显示指定的如果没显示指定&#xff0c;就找是否有默认参数的版本如果都…

Element Plus阻止 el-dropdown、el-switch等冒泡事件

最近做vue3项目&#xff0c;使用Element Plus,又遇到坑了&#xff01; 问题点&#xff1a;组件中遇到事件冒泡问题了&#xff0c;el-checkbox 中 change事件要求阻止冒泡&#xff0c;如下代码中要求点击checkbox时不调用li标签的show方法 <li click"show()">…

STM32物联网基于ZigBee智能家居控制系统

实践制作DIY- GC0169-ZigBee智能家居 一、功能说明&#xff1a; 基于STM32单片机设计-ZigBee智能家居 二、功能介绍&#xff1a; 1个主机显示板&#xff1a;STM32F103C最小系统ZigBee无线模块OLED显示器 语音识别模块多个按键ESP8266-WIFI模块&#xff08;仅WIFI版本有&…

Spring Cloud Pipelines 入门实践

文章目录 1. 前言2. Spring Cloud Pipelines 是做什么的2.1. 预定义的流程2.2. 集成测试和契约测试2.3.部署策略 4. Spring Cloud Pipelines的使用示例4.1. 创建一个Spring Boot应用4.2. 将代码托管到GitHub仓库4.3. 添加Spring Cloud Pipelines依赖4.4. 配置Spring Cloud Pipe…

生成Android证书

前提&#xff1a;确保本机安装好了java1.8 第一步 打开CMD keytool -genkey -alias AAAA -keyalg RSA -keysize 2048 -validity 36500 -keystore D:\mygitee\keyStore\szxApp.jksD:\mygitee\keyStore\szxApp.jks是证书要生成的位置 szxApp.jks 是证书名称 D:\mygitee\keySto…