【Interview】深入理解Semaphore源码

2024-05-13 07:58

本文主要是介绍【Interview】深入理解Semaphore源码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

  • Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
  • 比如数据库的连接资源是非常有限的,如果同时有上千个线程去数据获取连接,对数据造成的压力是非常的,会造成数据库无法连接而报错,Semaphore就可以限制此类问题
  • Semaphore有非公平和公平模式,默认是非公平的。当Semaphore设置为1时,可以排它锁使用,同一个时刻,只能限制一个线程访问。和CountDownLatch一样的,内部都有一个Sync内部类,基于AQS实现同步状态的释放和获取。

Semaphore提供的方法

  • Semaphore(int permits) 创建非公平的指定许可数的信号量
  • Semaphore(int permits, boolean fair) 创建指定的许可数和指定是否是公平模式的信号量
  • void acquire() 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
  • void acquire(int permits) 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。
  • int availablePermits() 返回此信号量中当前可用的许可数。
  • int getQueueLength() 返回正在等待获取的线程的估计数目。
  • boolean hasQueuedThreads() 查询是否有线程正在等待获取。
  • Collection<Thread> getQueuedThreads() 返回一个 collection,包含可能等待获取的线程。
  • reducePermits(int reduction) 根据指定的缩减量减小可用许可的数目。
  • release() 释放一个许可,将其返回给信号量。
  • release(int permits) 返回标识此信号量的字符串,以及信号量的状态。

使用方法

public class SemaphoreTest {static Semaphore s=new Semaphore(3); //非公平static  class Task implements   Runnable{@Overridepublic void run() {try {s.acquire();  //获取一个许可System.out.println(Thread.currentThread().getName()+" 获取一个许可开始执行...");Thread.sleep(1000);System.out.println(Thread.currentThread().getName()+" 退出。");}catch (Exception e){}finally {s.release();  //规划一个许可}}}public static void main(String[] args) {for (int i = 0; i <=5 ; i++) {new Thread(new Task()).start();}}
}
  • 输出结果,可以看出同一时刻只能三个线程进入执行,当有一个线程退出归还许可后,立马就会有其余线程去竞争这个多出的许可。
Thread-0 获取一个许可开始执行...
Thread-1 获取一个许可开始执行...
Thread-2 获取一个许可开始执行...
Thread-2 退出。
Thread-1 退出。
Thread-3 获取一个许可开始执行...
Thread-0 退出。
Thread-4 获取一个许可开始执行...
Thread-5 获取一个许可开始执行...
Thread-4 退出。
Thread-3 退出。
Thread-5 退出

源码分析

初始化一个信号时

  • 当执行new Semaphore(3)时,默认是非公平的实现方式。看看内部是如何是实现的
    public Semaphore(int permits) {sync = new NonfairSync(permits);}
  • 源码中可以看出是通过NonfairSync``这个内部类返回的一个实列,NonfairSyncSync子类。

acquire 获取一个许可

 public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);}
  • 获取许可时调用的是AQS中的acquireSharedInterruptibly方法,以共享模式获同步状态,如果被中断则中止。
    public final void acquireSharedInterruptibly(int arg)throws InterruptedException {// 判断线程是否中断if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)//获取同步状态失败 执行如下方法,这个方法以自旋的方式一直获取同步状态doAcquireSharedInterruptibly(arg);}
  • tryAcquireShared由Sync类提供实现,非公平模式调用NonfairSync的,否则调用FairSync类的方法

非公平

   static final class NonfairSync extends Sync {private static final long serialVersionUID = -2694183684443567898L;NonfairSync(int permits) {super(permits);}//获取锁protected int tryAcquireShared(int acquires) {// Sync类的非公平获取同步状态方法return nonfairTryAcquireShared(acquires);}}

公平

    static final class FairSync extends Sync {private static final long serialVersionUID = 2014338818796000944L;FairSync(int permits) {super(permits);}//公平模式获取信号量protected int tryAcquireShared(int acquires) {//自旋操作for (;;) {//公平模式,先要判断该线程是否位于CLH同步队列的列头if (hasQueuedPredecessors())return -1;//获取同步状态总数(信号量)int available = getState();//当前信号量减去获取的acquires个信号。int remaining = available - acquires;//CAS方式设置信号量。unsafe类提供实现,返回信号量if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}}
  • 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();//当前信号量减去获取的acquires个信号。int remaining = available - acquires;//CAS方式设置信号量。unsafe类提供实现,返回信号量if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}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))return current;}}}
  • 从源码中得出,Semaphore获取信号量许可时,公平和非公平的区别是,公平模式首先判断当前线程是否位于CLH同步队列的队列头中。
  • 而非公平模式是的竞争是抢占式,谁竞争到就谁获取。
  • 先是获取当前信号总数减去acquires个许可信号,然后以CAS方式设置CAS方式设置信号,并返回新的信号总数。CAS内部是依赖于AQS队列同步器中的unsafel类提供实现。

release归还许可

    public void release() {sync.releaseShared(1);}public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}
  • 释放信号量 release 首先会调用AQSreleaseShared.
  • tryReleaseShared 会调用SynctryReleaseShared方法
  • 释放操作时以自旋方式执行,首先获取总的信号建去要释放的信号量,然后判断这个信号量是否小于大于总的信号量,如果大于则抛出异常,否则以CAS的方式设置信号量
		//释放同步状态,也就是归还信号量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;}}

总结

  • 从源码中可以看出Semaphore的实现方式主要是依靠AQS实现的,以state同步状态成变量作为信号量的总数,获取和释放都是以CAS+自旋操作的方式设置state成员变量。
  • JDK并发包中的工具了哦都是AQS为基石实现的。大多都是使用CAS+自旋的方式去改变state来达到锁的获和释放

这篇关于【Interview】深入理解Semaphore源码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/985122

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-