JUC工具类: Semaphore详解

Semaphore底层是基于AbstractQueuedSynchronizer来实现的。Semaphore称为计数信号量,它允许n个任务同时访问某个资源,可以将信号量看做是在向外分发使用资源的许可证,只有成功获取许可证,才能使用资源。@立刀旁

目录

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

# Semaphore源码分析

# 类的继承关系

# 类的内部类

# 类的内部类 - Sync类

# 类的内部类 - NonfairSync类

# 类的内部类 - FairSync类

# 类的属性

# 类的构造函数

# 核心函数分析 - acquire函数

# 核心函数分析 - release函数

# Semaphore示例

# 更深入理解

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

# 场景问题

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

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

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

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

# 参考文章


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

提示

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

  • 什么是Semaphore?
  • 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个令牌,会获取到吗?

# Semaphore源码分析

# 类的继承关系

public class Semaphore implements java.io.Serializable {}

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

# 类的内部类

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

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

# 类的内部类 - 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类存在如下方法和作用如下。

# 类的内部类 - 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方法,表示按照非公平策略进行资源的获取。

# 类的内部类 - 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方法的源码可知,它使用公平策略来获取资源,它会判断同步队列中是否存在其他的等待节点。

# 类的属性

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

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

# 类的构造函数

  • 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。

# 核心函数分析 - acquire函数

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

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

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

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

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

# 核心函数分析 - release函数

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

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

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

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

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

# 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三个线程获取许可运行,根据结果,可能存在如下的一种时序。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# 更深入理解

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

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

# 场景问题

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

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

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

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

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

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

# 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) {}}
}

# 参考文章

  • 文章主要参考自leesf的https://www.cnblogs.com/leesf456/p/5414778.html,在此基础上做了增改。
  • https://blog.csdn.net/u010412719/article/details/94986327

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

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

相关文章

EDA 虚拟机 Synopsys Sentaurus TCAD 2016.03 下载

下载地址&#xff08;制作不易&#xff0c;下载使用需付费&#xff0c;不能接受的请勿下载&#xff09;&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1baw0IhmnFOKVkJMI3zkD_A?pwdcheo 提取码&#xff1a;cheo

【SQL】如何描述索引的分类

数据库中的索引种类多样&#xff0c;每种索引都有其特定的用途和特点。以下是常见的索引分类&#xff1a; 1. 单列索引 (Single-Column Index) 对单个列创建的索引。最基本的索引类型。 CREATE INDEX idx_column_name ON table_name (column_name);2. 多列索引 (Composite I…

联邦的基础配置

一、联邦的定义 联邦&#xff1a;在AS内部部署全互联的IBGP对等体可以很好解决IBGP路由传递的问题&#xff0c;但是扩展性低&#xff0c;大型网络中会带来沉重负担&#xff0c;针对此问题可以用路由反射器解决&#xff0c;也可以利用联邦解决&#xff0c;联邦也被称为联盟。大…

从Telnet到SSH:提升远程访问的安全性

前言 在计算机网络和远程通信中&#xff0c;Telnet和SSH是两种常用的协议&#xff0c;用于远程访问和管理服务器。然而&#xff0c;它们在安全性和功能方面有显著的区别。本文将详细讲述Telnet和SSH的区别&#xff0c;并探讨SSH在增强远程访问安全性中的重要性。 什么是Telne…

上门按摩SPA系统源码定制,专业搭建开发预约服务平台

在快节奏的现代社会&#xff0c;人们对放松和舒缓压力的需求日益增长。上门按摩SPA服务因其便捷性和私密性&#xff0c;受到了越来越多消费者的青睐。为了满足这一市场需求&#xff0c;专业的上门按摩SPA预约服务平台的搭建显得尤为重要。 我们的团队专注于为按摩SPA行业提供上…

MRTK 2.8.3

1.PC按键控制 移动摄像头:WSADQE、鼠标右键 模拟双手:左手(左Shift)、右手(右Shift) 将模拟的双手保持在视野中:T或Y 旋转模拟手部:按住Ctrl并移动鼠标 捏合手势:左Shift/空格 + 鼠标左键 2.常用脚本 (1)HandInteractionTouch(需搭配NearInteractionTouchableVolum…

python中方法的重写详解

python中对于类的继承的时候调用父类中一些方法的时候&#xff0c;会根据新的功能给方法增加一些新的内容&#xff0c;同时又要保留一些原有的父类里面的一些内容&#xff0c;此时就要对方法进行重写&#xff0c;这里面详细记录自己对于方法重写的心得 重写父类用两种&#xf…

使用Qt制作一个简单的界面

1、创建工程 步骤一&#xff1a; 步骤二&#xff1a; 步骤三&#xff1a; 选择 build system&#xff0c;有qmake、CMake 和 Qbs 三个选项。 CMake 很常用&#xff0c;功能也很强大&#xff0c;许多知名的项目都是用它&#xff0c;比如 OpenCV 和 VTK&#xff0c;但它的语法繁…

元宇宙相关技术研究--由AR眼镜引发的人机交互思考

本文是继上一篇文章基础上,跟踪最新VR/AR/MR眼镜技术发展的总结文章。随着IT新技术的发展,眼镜作为一种全新的交互方式,未来将逐渐走到人机交互的核心位置,眼镜集成了人机交互过程中的视觉(即眼睛)、听觉(即声音)、手势(即代替键盘鼠标触摸等)及方向位置感知等。这些交…

免费的CMS指纹识别系统

以下是一些免费的CMS指纹识别系统&#xff1a; WhatCMS&#xff1a;WhatCMS 是一个免费的在线工具&#xff0c;可以帮助用户快速识别网站所使用的CMS系统&#xff0c;包括WordPress、Joomla、Drupal等常见的CMS系统。用户只需输入网站的URL&#xff0c;即可获得识别结果。 ht…

如何成为C#编程高手?

成为C#编程高手需要时间、实践和持续的学习。以下是一些建议&#xff0c;可以帮助你提升C#编程技能&#xff1a; 深入理解基础知识&#xff1a; 确保你对C#的基本语法、数据类型、控制结构、面向对象编程&#xff08;OOP&#xff09;原则有深刻的理解。学习如何使用Visual Stud…

2024最新!将mysql的数据导入到Solr

Solr导入mysql的数据 如何安装导入数据前准备配置Solr的Jar包以及Mysql驱动包1.1、将solr-8.11.3\dist下的两个包进行移动1.2、将mysql-connect包也移动到该位置1.3、重启Solr项目 配置xml2.1、第一步我们需要创建核心2.2、第二步修改xml(这里是结合19年的教程)2.3、 创建data-…

mybatis延迟加载

mybatis延迟加载 1、延迟加载概述 应用场景 ​ 如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求&#xff0c;当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。 延迟加载的好处 ​ 先从单表查询、需要时再从关联表去关联查…

C++ 数据库MySQL 学习笔记(3) - 数据库操作

C 数据库MySQL 学习笔记(3) - 数据库操作 视图操作 视图是从一个或多个表中导出来的表&#xff0c;是一种虚拟存在的表。视图就像一个窗口&#xff0c;通过这个窗口可以看到系统专门提供的数据&#xff0c;这样用户可以不看整个数据库表中的数据&#xff0c;而只关心对自己有…

【加密与解密】【07】SSL安全套件全解析

SSL/TLS协议 SSL&#xff0c;Secure Socket Layer&#xff0c;安全套接层 TLS&#xff0c;Transport Layer Security&#xff0c;传输层安全协议 TLS是SSL的最终完善版本&#xff0c;一般也可称为SSL协议 SSL是负责传输层安全&#xff0c;确定传输层数据如何封装的一套协议…

讨论Nginx服务器的反爬虫和反DDoS攻击策略

Nginx服务器是一个高性能的Web服务器和反向代理服务器&#xff0c;具有强大的反爬虫和反DDoS攻击能力。本文将讨论Nginx服务器的反爬虫和反DDoS攻击策略&#xff0c;并给出相关的代码示例。 一、反爬虫策略 爬虫是一种自动化程序&#xff0c;用于从互联网上收集特定网站的数据…

Java 内存分页

1. 查询出要分页的总数 2. 根据 stream 流进行分页 3. 当分页数超过总数&#xff0c;返回空集合。例&#xff1a;总数10&#xff0c;页码2&#xff0c;pagesize10 4. 当最后一页不够 pageSize, limit(pageSize) 返回实际条数 例&#xff1a;12条数据&#xff0c;pageSize10…

使用Petalinux设计linux系统

文章目录 1.通过 Vivado 创建硬件平台&#xff0c;得到 hdf 硬件描述文件2.设置 Petalinux 环境变量3.创建 Petalinux 工程4.配置Petalinux 工程5.配置Linux内核6.配置Linux根文件系统7.配置设备树文件8.编译 Petalinux 工程9.制作BOOT.BIN启动文件10.制作SD启动卡 1.通过 Viva…

GaussDB关键技术原理:高性能(三)

GaussDB关键技术原理&#xff1a;高性能&#xff08;二&#xff09;从查询处理综述对GaussDB的高性能技术进行了解读&#xff0c;本篇将从查询重写RBO、物理优化CBO、分布式优化器、布式执行框架、轻量全局事务管理GTM-lite等五方面对高性能关键技术进行分享。 目录 3 高性能…

Jupyter Notebook相关

Jupyter Notebook 相关 一、什么是 Jupyter Notebook&#xff1f; 1.1 基本概念 Jupyter Notebook 是一个基于 web 的应用程序&#xff0c;可以用来创建和共享包含代码、文本、公式、图像和图表的文档。它类似于一个电子笔记本&#xff0c;你可以在一个页面上写代码并立即运行…