当前位置: 首页 > news >正文

Java并发进阶系列:深度讨论jdk1.8 ConcurrentHashMap并发环境下transfer方法桶位分配过程

在前面有多篇关于jdk1.8的ConcurrentHashMap研究是基于源代码给出的深度分析,要知道多线程环境下的ConcurrentHashMap内部运行机制是相对复杂的,好在IDEA提供的相关断点和Debug功能确实好用,使得多线程调试起来直观,通过这种方式能加深多线程操作CHM的执行流程。

前期准备

这部内容请参考文章中的小节部分,本文不再累赘。

使用埋点打印法观测

此方法相对繁琐,难度并不大,要求使用者对源代码设计足够理解,否则埋点位置不佳影响观测效果

1、测试代码

package concurrent.demo;public class ResizeStampBugTest {public static void main(String[] args) {// 设置64个线程并发putint maxThreads = 64;// 初始容量为8,内部会被调整为16ConcurrentHashMap<Long, String> map = new ConcurrentHashMap<>(8);for (int i = 0; i < maxThreads; i++) {Thread t = new Thread(() -> map.put(Thread.currentThread().getId(),Thread.currentThread().getName()));t.setName("Thread-"+i);t.start();// 因为多个线程并发执行不方便查看打印结果,可以让前一个线程领先后面线程一丁点,以便观察打印结果,当然也可以不需要,多执行几次看看打印结果即可。// Thread.sleep(1);}}
}

可以看到这里ConcurrentHashMap类用的是项目concurrent.demo包下的ConcurrentHashMap.java 源码文件

2、更改桶位分配步长,将源码的16改为4,方便观察

    private static final int MIN_TRANSFER_STRIDE = 4;// private static final int MIN_TRANSFER_STRIDE = 16;

3、transfer方法加入打印每个线程分配的桶位区间

                else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ?nextIndex - stride : 0))) {bound = nextBound;i = nextIndex - 1;advance = false;// 以下三行是新增代码String s=String.format("%s分配的捅位区间为:[%d,%d]并挂起",Thread.currentThread().getName(),bound,i);System.out.println(s);// 在这里加一句挂起已经分配好桶位区间的线程,用于观察线程LockSupport.park(this);}

4、其他地方需要埋入打印语句

pulVal方法:当key定位到的桶位为空,直接放入key节点。

            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null))) {// 埋入打印语句System.out.println(Thread.currentThread().getName() + "在桶位["+i+"]put入Node节点并退出");break;                   // no lock when adding to empty bin}}

addCount方法:

                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)){// 埋入打印语句System.out.println(Thread.currentThread().getName()+"后续进入扩容逻辑transfer方法");transfer(tab, nt);}}else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2)){// 埋入打印语句System.out.println(Thread.currentThread().getName()+"第1个进入扩容逻辑transfer方法");transfer(tab, null);}

5、预期执行

打印结果如下:

Thread-0在桶位[9]put入Node节点并退出
Thread-1在桶位[10]put入Node节点并退出
Thread-2在桶位[11]put入Node节点并退出
Thread-3在桶位[12]put入Node节点并退出
Thread-4在桶位[13]put入Node节点并退出
Thread-5在桶位[14]put入Node节点并退出
Thread-6在桶位[15]put入Node节点并退出
Thread-7在桶位[0]put入Node节点并退出
Thread-8在桶位[1]put入Node节点并退出
Thread-9在桶位[2]put入Node节点并退出
Thread-10在桶位[3]put入Node节点并退出
Thread-11在桶位[4]put入Node节点并退出
Thread-111个进入扩容逻辑transfer方法 # 注意此线程
Thread-12在桶位[5]put入Node节点并退出
Thread-12进入扩容逻辑transfer方法
Thread-13在桶位[6]put入Node节点并退出
Thread-13进入扩容逻辑transfer方法
Thread-14在桶位[7]put入Node节点并退出
Thread-14进入扩容逻辑transfer方法
Thread-15在桶位[8]put入Node节点并退出
Thread-12分配的捅位区间为:[12,15]并挂起
Thread-13分配的捅位区间为:[4,7]并挂起
Thread-14分配的捅位区间为:[0,3]并挂起
Thread-11分配的捅位区间为:[8,11]并挂起

transfer埋点打印

打印结果说明①

因为CHM的底层table容量为16也即有16个桶位, 此外使用线程ID作为节点的key,根据桶位定位算法i = (n - 1) & hash,前面16个线程(第0号线程到第15号线程)并发将16个key恰好能放到table16个桶位上,这也是为何将打印点放在putVal对应位置,线程put完后break退出后进入addCount,因此才有以下类似的打印:

Thread-0在桶位[9]put入Node节点并退出
Thread-1在桶位[10]put入Node节点并退出
Thread-2在桶位[11]put入Node节点并退出
Thread-3在桶位[12]put入Node节点并退出
Thread-4在桶位[13]put入Node节点并退出
Thread-5在桶位[14]put入Node节点并退出
Thread-6在桶位[15]put入Node节点并退出
Thread-7在桶位[0]put入Node节点并退出
Thread-8在桶位[1]put入Node节点并退出
Thread-9在桶位[2]put入Node节点并退出
Thread-10在桶位[3]put入Node节点并退出
Thread-11在桶位[4]put入Node节点并退出Thread-12在桶位[5]put入Node节点并退出Thread-13在桶位[6]put入Node节点并退出Thread-14在桶位[7]put入Node节点并退出Thread-15在桶位[8]put入Node节点并退出

打印结果说明②

在打印结果中你会发现Thread-11的特别之处,如下:这个线程在put节点后进入addCount,此时因为前面第0号到10号线程总共put 入了11个节点,当线程Thread-11去put节点完后发现此时CHM节点数量达到扩容阈值12(16*0.75),

...
Thread-10在桶位[3]put入Node节点并退出
Thread-11在桶位[4]put入Node节点并退出
# Thread-11 写入节点后发现s达到扩容阈值
Thread-11第1个进入扩容逻辑transfer方法
...

因此线程Thread-11进入addCount方法的分支2且进入了扩容逻辑transfer,而且是作为第1个线程进入扩容逻辑

这就是为何要在addCount以下位置埋入打印点,捕获第1个进入扩容逻辑transfer方法的线程:线程Thread-11

                else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2)){// 埋入打印语句System.out.println(Thread.currentThread().getName()+"第1个进入扩容逻辑transfer方法");transfer(tab, null);

打印结果说明③

因为继线程Thread-11put节点后使得s=12达到扩容阈值,因此后来的线程Thread-12、Thread-13、Thread-14在它们put完节点也发现需要扩容,因此也都进入的transfer方法,显然它们分别作为第2个、第3个、第4个进入transfer的线程。

...
Thread-11在桶位[4]put入Node节点并退出
Thread-11第1个进入扩容逻辑transfer方法
Thread-12在桶位[5]put入Node节点并退出
Thread-12进入扩容逻辑transfer方法
Thread-13在桶位[6]put入Node节点并退出
Thread-13进入扩容逻辑transfer方法
Thread-14在桶位[7]put入Node节点并退出
Thread-14进入扩容逻辑transfer方法
Thread-15在桶位[8]put入Node节点并退出
# 线程Thread-15没能进入扩容逻辑transfer方法
Thread-12分配的捅位区间为:[12,15]并挂起
Thread-13分配的捅位区间为:[4,7]并挂起
Thread-14分配的捅位区间为:[0,3]并挂起
Thread-11分配的捅位区间为:[8,11]并挂起

这里为何线程Thread-15没能进入transfer方法?还记得上面将桶位分配步长设为4的说明吗

    private static final int MIN_TRANSFER_STRIDE = 4;// private static final int MIN_TRANSFER_STRIDE = 16;

因为一开始CHM容量为16,也即16个桶位,又因为桶位步长设定为4,因此只能有4个线程能成功cas分配到桶位区间。由于线程Thread-11第1个先进入transfer方法、线程Thread-12第2个进入、线程Thread-13第3个进入、线程Thread-14第4个进入,这4个线程恰好“瓜分”完16个桶位

Thread-12分配的捅位区间为:[12,15]并挂起
Thread-13分配的捅位区间为:[4,7]并挂起
Thread-14分配的捅位区间为:[0,3]并挂起
Thread-11分配的捅位区间为:[8,11]并挂起

之后transferIndex=0,表示全部桶位已经分配出去,那么再来的线程Thread-15,恰好满足下面条件 transferIndex <= 0) ,因此Thread-15不能进入transfer并break结束

                if (sc < 0) {if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||transferIndex <= 0){break;

为什么线程Thread-11、Thread-12、Thread-13、Thread-14在transfer方法分配完桶位区间后没有退出?

这就是为何使用 LockSupport.park(this)将线程挂起的原因,以便持续观察CHM的桶位分配机制,同时也能将CHM容量16扩容到32的过程暂停,使得CHM停留在第一轮扩容的过程中,而且暂停在桶位分配完之后,节点迁移之前。
“使得CHM扩容处理流程暂停在第一轮扩容的过程中”,这有什么用处呢,请看下面:

打印结果说明④

在AddCount方法下面位置埋入打印语句:

                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||transferIndex <= 0){System.out.println(Thread.currentThread().getName()+"因无桶位可分配,此线程直接退出");break;}

目的就是考察线程Thread-11、Thread-12、Thread-13、Thread-14在transfer方法分配完桶位区间后并挂起,后续的Thread-15到Thread-63是如何安排的

打印如下:其实无需打印也能知道,因为Thread-15进入AddCount分支2后,transferIndex已经是0,也即无桶位可分配,只好break退出,后面来更多的其他线程也同理。

Thread-15在桶位[8]put入Node节点并退出
Thread-15因无桶位可分配,此线程直接退出
Thread-16因无桶位可分配,此线程直接退出
Thread-17因无桶位可分配,此线程直接退出
Thread-18因无桶位可分配,此线程直接退出
Thread-19因无桶位可分配,此线程直接退出
Thread-20因无桶位可分配,此线程直接退出
Thread-21因无桶位可分配,此线程直接退出
Thread-22因无桶位可分配,此线程直接退出
Thread-23因无桶位可分配,此线程直接退出
Thread-24因无桶位可分配,此线程直接退出
Thread-25因无桶位可分配,此线程直接退出
Thread-26因无桶位可分配,此线程直接退出
Thread-27因无桶位可分配,此线程直接退出
Thread-28因无桶位可分配,此线程直接退出
Thread-11分配的捅位区间为:[12,15]并挂起
Thread-13分配的捅位区间为:[4,7]并挂起
Thread-14分配的捅位区间为:[0,3]并挂起
Thread-12分配的捅位区间为:[8,11]并挂起
Thread-29因无桶位可分配,此线程直接退出
Thread-30因无桶位可分配,此线程直接退出
Thread-31因无桶位可分配,此线程直接退出
Thread-32因无桶位可分配,此线程直接退出
Thread-33因无桶位可分配,此线程直接退出
Thread-34因无桶位可分配,此线程直接退出
Thread-35因无桶位可分配,此线程直接退出
Thread-36因无桶位可分配,此线程直接退出
Thread-37因无桶位可分配,此线程直接退出
Thread-38因无桶位可分配,此线程直接退出
Thread-39因无桶位可分配,此线程直接退出
Thread-40因无桶位可分配,此线程直接退出
Thread-41因无桶位可分配,此线程直接退出
Thread-42因无桶位可分配,此线程直接退出
Thread-43因无桶位可分配,此线程直接退出
Thread-44因无桶位可分配,此线程直接退出
Thread-45因无桶位可分配,此线程直接退出
Thread-46因无桶位可分配,此线程直接退出
Thread-47因无桶位可分配,此线程直接退出
Thread-48因无桶位可分配,此线程直接退出
Thread-49因无桶位可分配,此线程直接退出
Thread-50因无桶位可分配,此线程直接退出
Thread-51因无桶位可分配,此线程直接退出
Thread-52因无桶位可分配,此线程直接退出
Thread-53因无桶位可分配,此线程直接退出
Thread-54因无桶位可分配,此线程直接退出
Thread-55因无桶位可分配,此线程直接退出
Thread-56因无桶位可分配,此线程直接退出
Thread-57因无桶位可分配,此线程直接退出
Thread-58因无桶位可分配,此线程直接退出
Thread-59因无桶位可分配,此线程直接退出
Thread-60因无桶位可分配,此线程直接退出
Thread-61因无桶位可分配,此线程直接退出
Thread-62因无桶位可分配,此线程直接退出
Thread-63因无桶位可分配,此线程直接退出

以上就是使用埋入打印点调试transfer机制,通过“print”来观察其运行机制先对入门,其实debug模式才是真正便捷的调试方式,继续查阅下面内容。

使用IDEA 断点debug方式观测

1、只需在transfer以下位置打一个断点即可,也即在桶位区间cas分配的逻辑里的advance=true,如下,
transfer break point位置

并将断点的Suspend设为Thread,如下:
Thread Option断点设定

2、启动debug,IDEA进入debug操作,此处无需图。

3、在Watchs栏目加入要“观测的变量”,这里当然是关注每个线程分配到的桶位区间的左边界bound和右边界i
Watchs加入bound和i变量观测
Frames栏目可以显示每个线程的方法调用栈,在点选Thread-11作为观察目标,你可以清晰看到Thread-11的方法调用栈:

transfer // 栈顶
addCount
putval
put
main主函数入口  // 栈底

点选“transfer”方法帧,然后右侧Variables栏目会展示该方法内部的局部变量值,由于我们想观测线程桶位区间的左边界bound和右边界i,因此在Watches加入这两个变量,图中可以非常直观看出Thread-11的桶位区间为[bound=12,i=15]

若想看其他线程分配的桶位,很简单,在下拉框选中其他线程即可,如Thread-12,可以在Watches栏目看到Thread-12的桶位区间为[bound=8,i=11]
transfer Thread12桶位区间
同理也可以看出Thread-13、Thread-14的界面,这里不再一一展示。

5、所观测的方法帧里面的变量和Watches对应
方法帧与watches变量对应

如上图所示,Watches提示无法找到局部变量i和局部bound变量,这是因为当前观测的是在addCount这个方法帧,显然

addCount内部没有i和bound变量。此外,也可从addCount的局部变量表Variables栏目看Watches是否在里面。

为何Frames只显示4个线程在RUNING状态?

答案已经在第一小节的“使用埋点打印法观测”后面给出了详细的解释,这里也简要说明:

(1)第0到第15号线程put完节点后,其中第0到第10号线程结束退出,第11号线程发现put完后CHM节点数量达到扩容阈值12,因此进入AddCount分支2,并作为第1个进入扩容逻辑transfer的线程,故Thread-11分配到了桶位区间[12,15]

(2)线程Thread-12、Thread-13、Thread-14 put完节点也因为s节点数量达到扩容阈值,都进入到transfer方法,也分配到对应的桶位区间

(3)等线程Thread-15 put完节点后,发现已无桶位可分配,因此Thread-15 结束

(4)其他剩余的线程第16到第63号线程也同样因为“已无桶位可分配而结束”

小结

可以看出,IDEA调试并发环境的CHM确实小有难度,最好能在掌握源代码情况下debug,通过这种实践而非源代码分析的观测方式,你能任意控制多线程并发执行流以及观测其内部协调机制、竞争机制,从而能深入掌握JUC并发设计和源代码实现。

相关文章:

Java并发进阶系列:深度讨论jdk1.8 ConcurrentHashMap并发环境下transfer方法桶位分配过程

在前面有多篇关于jdk1.8的ConcurrentHashMap研究是基于源代码给出的深度分析&#xff0c;要知道多线程环境下的ConcurrentHashMap内部运行机制是相对复杂的&#xff0c;好在IDEA提供的相关断点和Debug功能确实好用&#xff0c;使得多线程调试起来直观&#xff0c;通过这种方式能…...

【深度学习-Day 14】从零搭建你的第一个神经网络:多层感知器(MLP)详解

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...

fdisk和parted的区别

在Linux系统中&#xff0c;fdisk和parted是两种常用的分区工具。虽然它们都可以对硬盘进行分区&#xff0c;但在功能和适用范围上有显著的区别。 fdisk fdisk主要用于MBR&#xff08;主引导记录&#xff09;分区表的管理。MBR分区表有以下特点&#xff1a; 支持小于2TB的硬盘…...

springMVC拦截器,拦截器拦截策略设置

目录 1、MyInterceptor1 2、UserController 3、MvcConfig&#xff0c;拦截器4种拦截方法策略 做请求的校验&#xff0c;如果校验没有通过&#xff0c;直接返回&#xff0c;原来下面的处理&#xff0c;就不用处理了 将request进行拦截校验 将response进行拦截校验 preHandle…...

如何测试北斗卫星通讯终端的性能?

测试北斗卫星通讯终端的性能需从功能、性能、环境适应性、可靠性等多维度展开&#xff0c;以下是具体测试内容与方法&#xff1a; 一、基础功能测试 验证终端是否满足北斗系统的核心通讯功能。 &#xff08;1&#xff09;通信模式测试 短报文通信 测试终端发送 / 接收短报…...

基于MakeReal3D的虚拟预装系统:飞机装配效率与精度的双重突破

在航空制造领域&#xff0c;飞机部件的对接装配是飞机制造过程中的关键环节。传统的部件装配方式高度依赖操作人员的经验和反复调整&#xff0c;调姿过程耗时较长&#xff0c;且难以保证每次装配都能达到最优状态。随着虚拟现实技术的成熟&#xff0c;虚拟装配技术作为一种新兴…...

IP54是什么?

IP54是什么 定义 IP54是一种国际标准&#xff0c;用来指示设备的防护等级&#xff0c;该标准由国际电工委员会&#xff08;IEC&#xff09;制定&#xff0c;并在许多领域广泛使用13。IP是Ingress Protection的缩写&#xff0c;IP等级是针对电气设备外壳对异物侵入的防护等级。…...

Python异步编程详解

Python异步编程详解 引言 异步编程是Python中处理并发操作的重要方式&#xff0c;它允许程序在等待I/O操作时执行其他任务&#xff0c;从而提高程序的整体效率。本文将详细介绍Python异步编程的概念、实现方式以及实际应用场景。 1. 异步编程基础 1.1 什么是异步编程&#x…...

AUC与Accuracy的区别

下面分别解释下这两句话的含义及其原因&#xff0c;并说明 AUC 与 Accuracy&#xff08;准确率&#xff09;的区别&#xff1a; AUC 是阈值无关的指标   • 含义&#xff1a;在二分类问题中&#xff0c;模型通常会输出一个概率值或打分&#xff0c;需要设定一个阈值来将这些概…...

差分数组:原理与应用

一、什么是差分数组 差分数组是一种高效处理区间更新操作的数据结构技巧&#xff0c;特别适用于需要对数组的某个区间进行频繁增减操作的场景。差分数组的核心思想是通过存储相邻元素的差值而非元素本身&#xff0c;将区间操作转化为端点操作&#xff0c;从而将时间复杂度从O(…...

一些C++入门基础

关键字 图引自 C 关键词 - cppreference.com 命名空间 命名空间解决了C没办法解决的各类命名冲突问题 C的标准命名空间&#xff1a;std 命名空间中可以定义变量、函数、类型&#xff1a; namespace CS {//变量char cs408[] "DS,OS,JW,JZ";int cs 408;//函数vo…...

免费插件集-illustrator插件-Ai插件-路径尖角圆角化

文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.总结 1.介绍 本文介绍一款免费插件&#xff0c;加强illustrator使用人员工作效率&#xff0c;实现图形编辑中路径尖角圆角化。首先从下载网址下载这款插件https://download.csdn.net/download/m0_67316550/87…...

数据分析_商务运营考核指标体系搭建

以抖音电商中的小学教辅书籍业务为例&#xff0c;搭建对接达人的商务运营团队能力考核指标体系&#xff0c;涵盖达人筛选、合作管理、效果追踪和长期价值维护等核心环节&#xff0c;结合教育产品特性和商务运营目标&#xff0c;设计分层量化指标&#xff1a; 一、考核目标 围绕…...

基于Java的校运会管理系统【附源码】

湄洲湾职业技术学院 毕业设计&#xff08;论文&#xff09; 课题名称&#xff1a; 系 别&#xff1a; 专 业&#xff1a; 年 级&#xff1a; 姓 名&#xff1a; 学 号&#xff1a; 指导教师&#xff1a; 摘 要 用传统的方式来管理信息&#xff0c;一是耗时较长&#xff0c;二是…...

保证数据库 + redis在读写分离场景中事务的一致性

在 Spring Boot 中实现数据库与 Redis 的一致性&#xff0c;特别是处理读写分离时&#xff0c;确保数据修改的事务一致性是一个常见的挑战。因为 Redis 是一个内存数据库&#xff0c;通常用于缓存&#xff0c;而关系型数据库是持久化存储&#xff0c;两者之间的数据同步和一致性…...

【Redis】跳表结构

目录 1、背景2、跳表【1】底层结构【2】关键操作【3】redis使用跳表原因【4】特性 1、背景 redis中的跳表是一种有序数据结构&#xff0c;主要用于实现有序集合&#xff08;zset&#xff09;。跳表通过多级索引实现高效查找&#xff08;平均O(logN)时间复杂度&#xff09;&…...

Semaphore解决高并发场景下的有限资源的并发访问问题

在高并发编程的领域中&#xff0c;我们常常面临着对有限资源的激烈抢夺问题。而 Java 的 java.util.concurrent 包提供的 Semaphore &#xff0c;为我们提供了精准控制对有限资源并发访问的强大能力。 一、Semaphore&#xff1f; Semaphore&#xff0c;直译为 “信号量”&#…...

医学影像辅助诊断系统开发教程-基于tensorflow实现

源码下载地址: https://download.csdn.net/download/shangjg03/90873910 1. 简介 医学影像辅助诊断系统是利用计算机视觉和深度学习技术,帮助医生分析医学影像(如X光、CT、MRI等)并提供诊断建议的系统。本教程将指导你开发一个基于深度学习的胸部X光肺炎检测系统。 2. 准备…...

手动导出Docker进行并自动执行脚本命令的操作方法

若你已在 Docker 镜像里手动封装好文件,想让容器启动时自动执行 start.sh 脚本,可按以下步骤操作将镜像导出,同时确保启动时能自动执行脚本。 1. 提交当前容器为新镜像 假设你是在某个运行中的容器里进行文件封装操作的,要先把这个容器的当前状态提交为一个新的 Docker 镜…...

Mysql 中的日期时间函数汇总

前言 在 MySQL 中&#xff0c;处理日期和时间是非常常见的需求&#xff0c;MySQL中内置了大量的日期和时间函数&#xff0c;能够灵活、方便地处理日期和时间数据&#xff0c;本节就简单介绍一下 MySQL中内置的日期和时间函数&#xff0c;以便更好地利用这些函数来处理日期和时间…...

RabbitMQ Topic RPC

Topics(通配符模式) Topics 和Routing模式的区别是: topics 模式使⽤的交换机类型为topic(Routing模式使⽤的交换机类型为direct)topic 类型的交换机在匹配规则上进⾏了扩展, Binding Key⽀持通配符匹配(direct类型的交换机路 由规则是BindingKey和RoutingKey完全匹配) 在top…...

Conda环境管理:确保Python项目精准复现

探讨如何使用 Conda 有效地管理项目依赖&#xff0c;确保你的 Python 环境可以被精确复制和轻松共享 为什么依赖管理如此重要&#xff1f; 在开始具体操作之前&#xff0c;我们先来理解一下为什么环境依赖管理至关重要&#xff1a; 可复现性 (Reproducibility)&#xff1a;无…...

基于PyTorch的医学影像辅助诊断系统开发教程

本文源码地址: https://download.csdn.net/download/shangjg03/90873921 1. 简介 本教程将指导你使用PyTorch开发一个完整的医学影像辅助诊断系统,专注于胸部X光片的肺炎检测。我们将从环境搭建开始,逐步介绍数据处理、模型构建、训练、评估以及最终的系统部署。...

Vue3——Pinia

目录 什么是 Pinia&#xff1f; 为什么选择 Pinia&#xff1f; 基本使用 安装pinia 配置pinia 定义store 使用 持久化插件 什么是 Pinia&#xff1f; Pinia 是一个轻量级的状态管理库&#xff0c;专为 Vue 3 设计。它提供了类似 Vuex 的功能&#xff0c;但 API 更加简…...

Java中Collections工具类中常用方法详解

文章从工具类的概述、常用方法的作用、实现原理到使用注意事项&#xff0c;都进行了详细说明&#xff0c;供你参考。 Java中Collections工具类中常用方法详解 在Java开发中&#xff0c;集合是存储和处理数据的重要容器&#xff0c;而java.util.Collections工具类则提供了一组静…...

面经总目录——持续更新中

说明 本面经总结了校招时我面试各个公司的面试题目&#xff0c;每场面试后我都及时进行了总结&#xff0c;同时后期补充扩展了同类型的相近面试题&#xff0c;校招时从两个方向进行投递&#xff0c;视觉算法工程师和软件开发工程师&#xff08;C方向&#xff09;&#xff0c;所…...

电力设备智能化方案复盘

本文针对公司在售的一款电力设备智能化方案的运营情况进行复盘分析&#xff0c;提出一些基于研发人员角度的看法及建议&#xff0c;欢迎大家交流&#xff0c;因本人经验有限&#xff0c;多多包涵。具体的产品用途和公司名称不方便透露。 1.背景 本方案是针对电网配电侧中某关键…...

Rocketmq刷盘机制和复制机制区别及关系

在RocketMQ中&#xff0c;刷盘机制和复制机制是两种不同但相互协作的机制&#xff0c;分别解决数据持久化和数据高可用的问题。它们的核心区别与关系如下&#xff1a; 一、刷盘机制&#xff08;Flush Disk&#xff09; 目标&#xff1a;解决单机数据持久化问题&#xff0c;确保…...

HTB 赛季8靶场 - Puppy

nmap扫描全端口 Nmap scan report for 10.129.243.117 Host is up, received echo-reply ttl 127 (0.47s latency). Scanned at 2025-05-18 21:12:56 EDT for 551s Not shown: 65512 filtered tcp ports (no-response) Bug in iscsi-info: no string output. PORT STATE …...

频分复用信号在信道中的状态

频分复用是一种将信道总带宽划分为多个互不重叠的子频带&#xff0c;每路信号占用一个子频带以实现多路信号并行传输的复用技术。 1、基本概念和原理 频分复用&#xff08;Frequency Division Multiplexing, FDM&#xff09;的核心思想是通过频率划分实现多路信号共享同一物理…...

CSS之box-sizing、图片模糊、计算盒子宽度clac、(重点含小米、进度条案例)过渡

一、Box-sizing 在使用盒子模型时往往会出现由于border\ padding设置过大&#xff0c;从而导致的盒子被撑大的情况。 此时可以设置box-sizing: border-box (padding和boeder加起来设置的值不可超出width) 此时不会撑大盒子。可在初始化时一起设置 * { padding:0; maigin:…...

AliSQL:阿里巴巴开源数据库的技术革新与应用实践

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 在数据驱动的互联网时代&#xff0c;高性能、高可靠的数据库系统是支撑企业核心业务的关键。AliSQL作为阿里巴巴集团基于MySQL深度定制的开源分支&…...

(二十四)Java网络编程全面解析:从基础到实践

一、网络编程概述 网络编程是现代软件开发中不可或缺的重要组成部分&#xff0c;它使得不同计算机上的程序能够相互通信和数据交换。Java作为一门成熟的编程语言&#xff0c;从最初版本就提供了强大的网络编程支持&#xff0c;使得开发者能够相对轻松地构建网络应用程序。 1.…...

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Rotating Navigation (旋转导航)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— Rotating Navigation 组件 仓库地址&#xff1a;&#x1f517;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;&#x1f517;https://50-vue-projects.vercel.app/ ✨ 组件目标 &#x1f300…...

OpenCV 第6课 图像处理之几何变换(重映射)

1. 概述 简单来说,重映射就是把一副图像内的像素点按照规则映射到到另外一幅图像内的对应位置上去,形成一张新的图像。 因为原图像与目标图像的像素坐标不是一一对应的。一般情况下,我们通过重映射来表达每个像素的位置(x,y),像这样: g(x,y)=f(h(x,y)) 在这里g()是目标图…...

传输层协议:UDP和TCP

1.传输层概念 传输层主要负责两台主机之间的数据传输&#xff0c;使数据从发送端到接收端。 端口号 端口号标识了一个主机上进行通信的不同的应用程序。 传输层接收到数据后&#xff0c;是要给到具体的应用层的进程。所以发送端的传输层封装报文时&#xff0c;就要添加上将来…...

[Linux] Linux线程信号的原理与应用

Linux线程信号的原理与应用 文章目录 Linux线程信号的原理与应用**关键词****第一章 理论综述****第二章 研究方法**1. **实验设计**1.1 构建多线程测试环境1.2 信号掩码策略对比实验 2. **数据来源**2.1 内核源码分析2.2 用户态API调用日志与性能监控 **第三章 Linux信号的用法…...

MySQL 库的操作 -- 字符集和校验规则,库的增删查改,数据库的备份和还原

目录 1. 字符集和校验规则 1.1 查看系统默认字符集以及检验规则 1.2 查看数据库支持的字符集 1.3 查看数据库支持的字符集检验规则 1.4 校验规则对数据库的影响 2. 数据库的操作 2.1 创建数据库 2.1.1 语法 2.1.2 示例 2.2 查看数据库 2.2.1 语法 2.2.2 查看当前使…...

Opencv常见学习链接(待分类补充)

文章目录 1.常见学习链接 1.常见学习链接 1.Opencv中文官方文档 2.Opencv C图像处理&#xff1a;矩阵Mat 随机数RNG 计算耗时 鼠标事件 3.Opencv C图像处理&#xff1a;亮度对比度饱和度高光暖色调阴影漫画效果白平衡浮雕羽化锐化颗粒感 4.OpenCV —— 频率域滤波&#xff…...

Docker网络全景解析:Overlay与Macvlan深度实践,直通Service Mesh集成核心

一、Docker网络基石&#xff1a;从单机到跨主机的本质跨越 1.1 网络模式全景图 Docker原生网络架构&#xff1a; ├─ 单机网络&#xff08;默认&#xff09; │ ├─ bridge&#xff1a;默认NAT模式&#xff08;docker0网桥&#xff09; │ ├─ host&#xff1a;共…...

十五、面向对象底层逻辑-BeanDefinitionRegistryPostProcessor接口设计

一、引言&#xff1a;Spring容器启动的核心枢纽 在Spring容器的启动过程中&#xff0c;BeanDefinitionRegistryPostProcessor接口是开发者深度介入Bean定义注册阶段的核心扩展点。作为BeanFactoryPostProcessor的子接口&#xff0c;它赋予了开发者对BeanDefinitionRegistry的直…...

图表组件库TeeChart Pro VCL/FMX :简化复杂数据处理的可视化利器

在数据驱动决策的时代&#xff0c;把复杂的数据转化为直观、易懂的图表&#xff0c;是很多人都面临的挑战。TeeChart Pro VCL/FMX 就是一款能帮你解决这个难题的强大工具&#xff0c;它为开发者和数据分析师提供了丰富的功能&#xff0c;让数据可视化变得简单又高效。 一、丰富…...

从客厅到驾驶舱:FSHD 如何成为全场景显示「破局者」

一、当显示技术遇到「场景困境」&#xff1a;传统方案的三大痛点 周末午后的客厅&#xff0c;阳光透过纱窗洒在幕布上&#xff0c;传统投影机的画面瞬间发白&#xff1b;高速公路上&#xff0c;车载 HUD 在强光下字迹模糊&#xff0c;驾驶员不得不频繁低头看仪表盘&#xff1b;…...

时源芯微|开关电源电磁干扰的控制技术

要有效解决开关电源的电磁干扰问题&#xff0c;可从以下三个关键方面着手&#xff1a;其一&#xff0c;降低干扰源产生的干扰信号强度&#xff1b;其二&#xff0c;阻断干扰信号的传播路径&#xff1b;其三&#xff0c;提升受干扰体的抗干扰能力。基于此&#xff0c;开关电源电…...

动态规划之爬楼梯模型

文章目录 爬楼梯模型LeetCode 746. 使用最小花费爬楼梯思路Golang 代码 LeetCode 377. 组合总和 Ⅳ思路Golang 代码 LeetCode 2466. 统计构造好字符串的方案数思路Golang 代码 LeetCode 2266. 统计打字方案数思路Golang 代码 爬楼梯模型 爬楼梯模型是动态规划当中的一个经典模型…...

图论学习笔记 3

自认为写了很多&#xff0c;后面会出 仙人掌、最小树形图 学习笔记。 多图警告。 众所周知王老师有一句话&#xff1a; ⼀篇⽂章不宜过⻓&#xff0c;不然之后再修改使⽤的时候&#xff0c;在其中找想找的东⻄就有点麻烦了。当然⽂章也不宜过多&#xff0c;不然想要的⽂章也不…...

进阶知识:自动化测试框架开发之无参的函数装饰器

进阶知识&#xff1a;自动化测试框架开发之无参的函数装饰器 from functools import wrapsdef func_2(func):"""无参的函数装饰器:param func::return:"""wraps(func)def wrap_func(*args, **kwargs):print(开始执行&#xff1a; func.__name__…...

【实验增效】5 μL/Test 高浓度液体试剂!Elabscience PE Anti-Mouse Ly6G抗体 简化流式细胞术流程

产品概述 Elabscience 推出的 PE Anti-Mouse Ly6G Antibody (1A8, 货号: E-AB-F1108D) 是一款高特异性、高灵敏度的流式抗体&#xff0c;专为小鼠中性粒细胞&#xff08;Ly6G⁺细胞&#xff09;的精准检测而设计。该抗体采用PE&#xff08;藻红蛋白&#xff09;标记&#xff0…...

多模态光学成像革命:OCT、荧光与共聚焦的跨尺度融合新范式

一、技术融合的必然性 在生物医学成像领域,单一模态往往存在视野-分辨率悖论。光学相干断层扫描(OCT)虽能实现毫米级深度扫描(1010mm视野),但其微米级分辨率难以解析亚细胞结构;荧光显微成像(FMI)虽具有分子特异性标记优势(88mm中观视野),却受限于光毒性及穿透深度…...

CHI中ordering的抽象

上图是一个典型的读操作过程中的几个流程&#xff0c;其中&#xff1a; compdata, 这个就是CHI协议保序之Comp保序-CSDN博客中提到的&#xff0c;comp保序&#xff0c;它实现的功能是&#xff0c;通知这个请求的RN, 你的请求&#xff0c;我已经开始处理了&#xff0c;同时也相当…...