【源码】【Java并发】【ThreadLocal】适合中学者体质的ThreadLocal源码阅读
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》
前言
经过了上一篇的学习,聪明的你一定知道了ThradLocal的怎么样使用的。
【Java并发】【ThreadLocal】适合初学体质的ThreadLocal
下面,跟上主播的节奏,马上开始ThreadLocal源码的阅读( ̄▽ ̄)"
内部结构
如下图所示,我们可以知道,每个线程,都有自己的threadLocals字段,指向ThreadLocalMap。
ThreadLocalMap中有一个Entry数组(table),用来存储我们set进ThreadLocal的值。
Entry的key指向ThreadLocal(弱引用),value就是我们set的值(强引用)。

Set流程

// set方法入口
public void set(T value) {Thread t = Thread.currentThread();// 获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {// 设置值map.set(this, value);} else {// 为当前线程创建ThradLocalMapcreateMap(t, value);}
}// getMap方法。获取当前线程,ThreadLocalMap
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
map为null的情况,开始初始化
初始化
void createMap(Thread t, T firstValue) {// 为当前线程设置ThreadLocalMap// key是TheadLocal,value是我们要塞入ThreadLocal线程的值t.threadLocals = new ThreadLocalMap(this, firstValue);
}
具体new的逻辑
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {// 设置table大小,初始容量为16。ThreadLocalMap的table就是用来存Entry的。table = new Entry[INITIAL_CAPACITY]; // 哈希算法算法,决定新的Entry插入到哪个槽里,后面会具体说这个。int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);// 当前在ThreadLocalMap中的Entry数量size = 1;// 设置扩容的阈值,2/3的时候扩容setThreshold(INITIAL_CAPACITY);
}
初始容量,16个,强制要求为2的幂次,用于优化位运算性能。通过静态final修饰确保全局唯一且不可修改。
/*** The initial capacity -- MUST be a power of two.*/
private static final int INITIAL_CAPACITY = 16;
扩容阈值,2/3 的时候扩容
/*** Set the resize threshold to maintain at worst a 2/3 load factor.*/
private void setThreshold(int len) {threshold = len * 2 / 3;
}
好了,让我们具体来说说说是怎么哈希的
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
为什么是 &? firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)?
当容量 INITIAL_CAPACITY 是 2 的幂(如 16)时,INITIAL_CAPACITY - 1 的二进制形式为全 1(例如 15 -> 1111)。此时,hash & (INITIAL_CAPACITY - 1) 等效于 hash % INITIAL_CAPACITY,但位运算的效率远高于取模运算。
firstKey.threadLocalHashCode,这个咋来的?
private final int threadLocalHashCode = nextHashCode();// nextHashCode方法
private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
}// nextHashCode,从0开始的hashCode
private static AtomicInteger nextHashCode = new AtomicInteger();// HASH_INCREMENT
private static final int HASH_INCREMENT = 0x61c88647;
为什么这个魔数是0x61c88647?
0x61c88647 近似于 (√5 - 1)/2 * 2^32(黄金分割比例的 32 位扩展)。这种设计确保哈希码在 2 的幂容量下均匀分布,减少冲突概率。
map不为null的情况,直接set
private void set(ThreadLocal<?> key, Object value) {// ThreadLocalMap的table,table是个数组里面是放Entry的Entry[] tab = table;int len = tab.length// hash到哪个槽位(上面初始化的时候,具体有说)int i = key.threadLocalHashCode & (len-1);// 判断当前的槽是否已经有Entry了// - 如果有:就for循环table,看是不是这次要更新的槽。// - 是的话更新。// - 不是的话,说明存在哈希冲突,需要通过开放地址法,找到为空的槽插入。// - 如果没有:就不执行下面的for循环,直接插入即可。// ===== ThreadLocal哈希冲突解决 =====// 我们都知道,这里的哈希冲突解决方案是 ---》 开放地址法(线性探测法)// 这里的处理:如果enty不为null的话,就一直i+1往下找下一个,直到table[i] == null。//(下面有nextIndex的代码,就是i+1。)for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();// 如果当前Entry的key,就是这次set需要的Entry的key,这里就更新if (k == key) {e.value = value;return;}// 如果这个Entry的key为null(可能是线程执行完被GC了)if (k == null) {// 清除这个Entry(后面会具体说怎么清除的)replaceStaleEntry(key, value, i);return;}}// 对该槽赋值tab[i] = new Entry(key, value);// table中Entry数量+1int sz = ++size;// 1、cleanSomeSlots清理没用的槽// 2、如果清空失败 并且 table中的Entry数量大于等于扩容阈值if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash(); // 重新hash
}
寻找下一个地址的方法nextIndex。
private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);
}
rehash做了什么?
private void rehash() {// 清除过期的Entry(Entry的key为null就是过期的意思,后面会说具体说怎么清理的)expungeStaleEntries();// Use lower threshold for doubling to avoid hysteresis// 通过主动提前扩容,防止哈希表在临界负载时因性能骤降影响用户体验// 当前Entry的数量 大于等于 扩容阈值的1/2if (size >= threshold - threshold / 4)resize();
}
为什么是threshold - threshold / 4?
因为要保持低附载。所以我们也可以说这是1 / 2扩容。
在开放寻址法中,通过主动降低扩容阈值(从 2/3 容量降至 3/4 * 2/3 = 1/2 容量),确保哈希表始终处于低负载状态,从而:减少线性探测的冲突概率,平滑性能波动,避免滞后现象,以可控的内存增长换取稳定的高性能。
那resize方法做了什么捏?省流版:扩容2倍扩容,旧的table里的Entry,重新哈希放入新的table里。
private void resize() {Entry[] oldTab = table;int oldLen = oldTab.length;// 2倍当前长度大小int newLen = oldLen * 2;Entry[] newTab = new Entry[newLen];int count = 0;// 遍历旧table里的Entry,将Entry重新哈希,放入到新的table里for (int j = 0; j < oldLen; ++j) {Entry e = oldTab[j];if (e != null) {ThreadLocal<?> k = e.get();// 清理失效的Entryif (k == null) {e.value = null; } else {// 哈希到新的槽位int h = k.threadLocalHashCode & (newLen - 1);// 开放地址法解决哈希冲突,如果这个节点有值了,就i+1找下一个槽位while (newTab[h] != null)h = nextIndex(h, newLen); // h+1,寻找下一个槽位// 新table里,节点的赋值newTab[h] = e;// 旧table的有效Entry数量 + 1count++;}}}// 设置新的扩容阈值(数组长度的 2/3)setThreshold(newLen);// 修改为新的table的sizesize = count;// 指向新的tabletable = newTab;
}// 设置新的扩容阈值
private void setThreshold(int len) {threshold = len * 2 / 3;
}
Get流程

get方法入口
public T get() {Thread t = Thread.currentThread();// 获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {// 根据当前线程的ThreadLocal获取EntryThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 当前线程没有ThreadLocalMapreturn setInitialValue();
}
获取Entry,getEntry方法。
private Entry getEntry(ThreadLocal<?> key) {// 获取槽位int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];// 如果这个槽不为null 且 这个槽位Entry的key是当前的ThreadLocalif (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e); // 当前槽找不到这次要获取的key
}
如果找不到,执行getEntryAfterMiss方法。
为什么会找不到?
可能是真没有,也有可能是哈希冲突导致的槽位一直++,所以Entry被放入到了后面。
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;while (e != null) {ThreadLocal<?> k = e.get();// 找到了返回if (k == key)return e;// 清除过期的Entryif (k == null)expungeStaleEntry(i);elsei = nextIndex(i, len); // 获取下一个槽位// e变为下一个Entrye = tab[i];}// 实在找不到return null;
}
我们来看看,如果当前线程没有ThreadLocalMap,会怎么处理setInitialValue方法
private T setInitialValue() {// 初始value值,默认为null。子类可以重写,返回不同的初始值。T value = initialValue();Thread t = Thread.currentThread();// 获取当前线程的ThreadLocal。(上面有说过这个方法,就是t.threadLocals。)ThreadLocalMap map = getMap(t);// 这个和的set流程一样了。有就set初始值进去,没有就创建map。if (map != null) {map.set(this, value);} else {createMap(t, value);}// 返回初始值return value;
}
initialValue,默认返回null,子类可以重写。
protected T initialValue() {return null;
}
下面,主播为大家表演一手,子类重写。
// 子类重写
public class MyThreadLocal extends ThreadLocal {@Overrideprotected Object initialValue() {return "aaaa";}
}// 测试类
public class MyThreadLocalTest {public static void main(String[] args) {MyThreadLocal myThreadLocalTest = new MyThreadLocal();String o = (String) myThreadLocalTest.get();System.out.println(o); // 输出aaaa}
}
删除Entry
在ThreadLocal中存在2种删除方法。
探测删除(线性探测清理)
-
对应方法:
expungeStaleEntry(int staleSlot) -
核心目标:清理当前过期的 Entry,并重新整理哈希表,解决因哈希冲突导致的 Entry 位置偏移问题。
-
省流版流程:做了2件事情。
- 清空当前Entry 。
- 判断后续Entry是否需要清除,需要就清除,不需要的话,就重新哈希放到table里。
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// 清理当前槽点的数据tab[staleSlot].value = null;tab[staleSlot] = null;size--;Entry e;int i;// 从当前槽点开始向后遍历,判断每个Entry是否需要清空for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();// 失效的Entry,就给它清除if (k == null) {e.value = null;tab[i] = null;size--;} else {// ====没有失效的Entry====// 重新哈希槽位,Entry重新放入table中int h = k.threadLocalHashCode & (len - 1);if (h != i) {tab[i] = null;// 哈希冲突,开放址法while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}// 返回停止的槽位return i;
}
⚠️ 探测性修复是有局限性的,并不会从当前要删除的槽点一直往后遍历到table的所有槽点,它也会在某个为null的槽点中停下来。(🤔不过话又说回来了,真扫了整个table,性能包炸的)
for (i = nextIndex(staleSlot, len);(e = tab[i]) != null; // 当tab[i]为null会停止清除,即使tab[i+1]是过期的Entryi = nextIndex(i, len))
启发式清除(概率性扫描)
对应方法:cleanSomeSlots(int i, int n)
核心目标:以较低的成本扫描部分槽位,清理可能的过期 Entry,避免全表扫描的性能开销。
// 入参解释:
// - i,一个为null的槽位。
// - n,一般传的是table的长度。
private boolean cleanSomeSlots(int i, int n) {boolean removed = false;Entry[] tab = table;int len = tab.length;// 一直修改len的值,如果len的默认值为16,16 -> 8 -> 4 -> 2 -> 0do {i = nextIndex(i, len);Entry e = tab[i];// 当前节点是过期的if (e != null && e.get() == null) {n = len;removed = true;// 调用探测删除方法i = expungeStaleEntry(i);}} while ( (n >>>= 1) != 0);return removed;
}
while ((n >>>= 1) != 0) 是一种高效且巧妙的设计:
- 位运算控制循环次数:以
O(log n)时间实现启发式扫描。 - 平衡性能与清理效果:避免全表扫描,但覆盖足够多的槽位。
- 自适应哈希表容量:天然适配 2 的幂次容量的哈希表。
Remove流程
上面介绍了Entry是怎么删除的,这里来看看,remove到底干了什么勒?😄
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread()); // 获取当前线程的ThreadLocalMapif (m != null)m.remove(this);}
具体的remove方法,我们总结下做了啥?
- 哈希开始的槽位,找到要删除的Entry
- 断开Entry对ThreadLocal key的弱引用(置为null)
- 探测式删除Entry。
private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;// 获取开始的槽位int i = key.threadLocalHashCode & (len-1);// 可能出现哈希冲突,所以要一直向后找Entry。for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear(); // 删除key的弱引用expungeStaleEntry(i); // 上面介绍过勒,这个是探测式删除。return;}}
}
为什么会内存泄漏?
这就不得不提到我们的Entry了,我们可以看到,我们的Key是一个弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k); // 调用父类 WeakReference 的构造函数,将 k 作为弱引用存储value = v;}
}
那么,弱引用会怎么样勒?来主播带大家回忆下,Java的四种引用:
强引用 (Strong Reference)
- 清除时机:当对象没有任何强引用指向时,会在下一次GC时被回收。即使内存不足,也不会被回收(可能导致OOM)。
Object obj = new Object(); // 强引用
软引用 (SoftReference)
- 清除时机:当内存不足时(即将抛出OutOfMemoryError前),JVM会尝试回收。
SoftReference<Object> softRef = new SoftReference<>(new Object());
弱引用 (WeakReference)
- 清除时机:只要发生垃圾回收,无论内存是否充足,对象都会被回收。
WeakReference<Object> weakRef = new WeakReference<>(new Object());
虚引用 (PhantomReference)
- 清除时机: 对象被回收后,虚引用会被放入关联的ReferenceQueue,需手动处理。无法通过虚引用获取对象实例,仅用于追踪对象回收状态。
// 1. 必须绑定引用队列
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object heavyObject = new Object(); // 假设这是一个占用大量资源的对象// 2. 创建虚引用(必须关联队列)
PhantomReference<Object> phantomRef = new PhantomReference<>(heavyObject, queue);// 3. 关键特性:无法通过虚引用获取对象
System.out.println(phantomRef.get()); // 输出 null ❌// 4. 手动解除强引用(触发回收条件)
heavyObject = null;// 5. 强制触发垃圾回收
System.gc();// 6. 检查队列:当对象被回收后,虚引用会被加入队列
Reference<?> ref = queue.remove(500); // 阻塞500ms等待队列
if (ref == phantomRef) {System.out.println("对象已被回收,可执行后续清理操作 ✅");// 例如:若对象管理了堆外内存,可在此释放
}
ok,前面铺垫了这么久,让我们分析下,内存泄漏的原因:
Key 的弱引用特性
- 当
ThreadLocal实例失去强引用时(例如threadLocal = null),由于Entry的 key 是弱引用,GC 会回收该ThreadLocal实例,此时Entry的 key 变为null。 - 问题:
Entry的value仍是强引用,无法被自动回收。

线程长期存活
如果线程是线程池的核心线程(长期存活),它的 ThreadLocalMap 会一直存在。即使 key 被回收,value 仍然通过 Entry 的强引用保留在内存中。
代码模拟,没有及时remove,导致OOM:
public class ThreadLocalOOMExample {// 设置 JVM 参数:-Xmx64m -Xms64m (限制堆内存为 64MB)public static void main(String[] args) throws InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(5); // 线程池复用线程for (int i = 0; i < 1000; i++) { // 提交 1000 个任务executor.submit(() -> {ThreadLocal<BigObject> threadLocal = new ThreadLocal<>();try {// 每个任务创建一个 1MB 的对象,存到 ThreadLocal 中threadLocal.set(new BigObject());// 模拟业务逻辑(不调用 remove())} finally {// 此处故意不调用 threadLocal.remove()}});Thread.sleep(10); // 控制任务提交速度}executor.shutdown();}static class BigObject {// 每个对象占用 1MB 内存private final byte[] data = new byte[1024 * 1024];}
}
运行结果
Exception in thread "pool-1-thread-2" java.lang.OutOfMemoryError: Java heap spaceat ThreadLocalOOMExample$BigObject.<init>(ThreadLocalOOMExample.java:23)at ThreadLocalOOMExample.lambda$main$0(ThreadLocalOOMExample.java:15)...
后话
聪明的你,是不是对ThreadLocal有更多的了解呢?
- ThreadLocalMap的数据结构是怎么样
- value是怎么set进去
- 怎么get到我们set的value
- 如何删除过期的Entry
- 内存泄漏的可能原因
相信聪明的你一定有了答案(‾◡◝)
相关文章:
【源码】【Java并发】【ThreadLocal】适合中学者体质的ThreadLocal源码阅读
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 👍 欢迎点赞、收藏、关注,跟上我的更新节奏 📚欢迎订阅专栏…...
背包问题模板
文章目录 01背包题意思路代码优化 完全背包题意思路代码优化 多重背包题意思路代码优化 分组背包题意思路代码 01背包 特点:每件物品最多只能用一次 01背包问题 题意 给出每件物品的体积v,价值w,求解能装入背包的的物品的最大价值,并且每件物品只能选一…...
Redis 处理读请求
在前文“Redis 接收连接”中,Redis 将接收的客户端连接加入了 epoll 中监听,同时还设置了读事件处理器 connSocketEventHandler。 假设现在客户端向 Redis 发来一条 set key value 命令。 事件循环 aeProcessEvents 在事件循环 aeProcessEvents 中会调…...
Sentinel源码—8.限流算法和设计模式总结二
大纲 1.关于限流的概述 2.高并发下的四大限流算法原理及实现 3.Sentinel使用的设计模式总结 3.Sentinel使用的设计模式总结 (1)责任链模式 (2)监听器模式 (3)适配器模式 (4)模版方法模式 (5)策略模式 (6)观察者模式 (1)责任链模式 一.责任链接口ProcessorSlot 二.责…...
VulnHub-DarkHole_1靶机渗透教程
VulnHub-DarkHole_1靶机渗透教程 1.靶机部署 [Onepanda] Mik1ysomething 靶机下载:https://download.vulnhub.com/darkhole/DarkHole.zip 直接使用VMware打开就行 导入成功,打开虚拟机,到此虚拟机部署完成! 注意:…...
Keil MDK‑5 中使用 GNU ARM GCC 的 -Wno-* 选项屏蔽编译警告
在项目编译过程中,我们常常会遇到许多警告提示;而在有些情况下,当我们已经了解这些警告的原因时,可以选择忽略它们,从而减少干扰,集中精力修复其他更重要的问题。 一、添加屏蔽警告的编译选项 (…...
边缘计算全透视:架构、应用与未来图景
边缘计算全透视:架构、应用与未来图景 一、产生背景二、本质三、特点(一)位置靠近数据源(二)分布式架构(三)实时性要求高 四、关键技术(一)硬件技术(二&#…...
爬虫学习——下载文件和图片、模拟登录方式进行信息获取
一、下载文件和图片 Scrapy中有两个类用于专门下载文件和图片,FilesPipeline和ImagesPipeline,其本质就是一个专门的下载器,其使用的方式就是将文件或图片的url传给它(eg:item[“file_urls”])。使用之前需要在settings.py文件中对其进行声明…...
路由器转发规则设置方法步骤,内网服务器端口怎么让异地连接访问的实现
在路由器上设置端口转发(Port Forwarding)可以将外部网络流量引导到特定的局域网设备,这对于需要远程访问服务器、摄像头、游戏主机等设备非常有用。 登录路由器管理界面,添加端口转发规则让外网访问内网的实现教程分享。以下是设…...
MQ底层原理
RabbitMQ 概述 RabbitMQ 是⼀个开源的⾼性能、可扩展、消息中间件(Message Broker),实现了 Advanced Message Queuing Protocol(AMQP)协议,可以帮助不同应⽤程序之间进⾏通信和数据交换。RabbitMQ 是由 E…...
本地部署DeepSeek-R1模型接入PyCharm
以下是DeepSeek-R1本地部署及接入PyCharm的详细步骤指南,整合了视频内容及官方文档核心要点: 一、本地部署DeepSeek-R1模型 1. 安装Ollama框架 下载安装包 访问Ollama官网(https://ollama.com/download)Windows用户选择.exe文件,macOS用户选择.dmg包。 安装验证 双击…...
jvm-描述符与特征签名的区别
在Java虚拟机(JVM)中,存储的是方法签名,而不是仅仅方法描述符。方法签名包含了方法的参数类型和返回值类型的信息,而方法描述符通常指的是仅包含参数类型的那部分信息。为了更清晰地理解这两者的区别以及它们如何在JVM…...
Java基于SpringBoot的企业车辆管理系统,附源码+文档说明
博主介绍:✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇&…...
QT调用ffmpeg库实现视频录制
可以通过QProcess调用ffmpeg命令行,也可以直接调用ffmpeg库,方便。 调用库 安装ffmpeg ffmpeg -version 没装就装 sudo apt-get update sudo apt-get install ffmpeg sudo apt-get install ffmpeg libavdevice-dev .pro引入库路径,引入库 LIBS += -L/usr/lib/aarch64-l…...
百度 Al 智能体心响 App 上线
百度 AI 智能体心响 App 于 2025 年 4 月 17 日低调上线安卓应用市场,4 月 22 日正式登陆各大安卓应用市场。以下是对这款应用的详细介绍: 产品定位:这是一款以 “AI 任务完成引擎” 为核心的手机端超级智能体产品,定位为 “复杂任…...
进阶篇 第 2 篇:自相关性深度解析 - ACF 与 PACF 图完全指南
进阶篇 第 2 篇:自相关性深度解析 - ACF 与 PACF 图完全指南 (图片来源: Negative Space on Pexels) 欢迎来到进阶系列的第二篇!在上一篇,我们探讨了更高级的时间序列分解技术和强大的指数平滑 (ETS) 预测模型。ETS 模型通过巧妙的加权平均捕…...
【Java面试笔记:基础】7.int和Integer有什么区别?
在Java中,int和Integer虽然都用于表示整数值,但它们在本质、用法和特性上有显著差异。 1. int 和 Integer 的区别 int: 原始数据类型:int 是 Java 的 8 个原始数据类型之一,用于表示整数。性能优势:直接存…...
鸿蒙移动应用开发--渲染控制实验
任务:使用“对象数组”、“ForEach渲染”、“Badge角标组件”、“Grid布局”等相关知识,实现生效抽奖卡案例。如图1所示: 图1 生肖抽奖卡实例图 图1(a)中有6张生肖卡可以抽奖,每抽中一张,会通过弹层显示出来…...
AI代表企业签订的合同是否具有法律效力?
AI代表企业签订的合同是否具有法律效力? 首席数据官高鹏律师团队编著 在数字经济高速发展的今天,人工智能(AI)技术已广泛应用于商业活动,包括合同起草、审查甚至签署环节。然而,AI代表企业签订的合同是否具…...
安宝特分享|AR智能装备赋能企业效率跃升
AR装备开启智能培训新时代 在智能制造与数字化转型浪潮下,传统培训体系正面临深度重构。安宝特基于工业级AR智能终端打造的培训系统,可助力企业构建智慧培训新生态。 AR技术在不同领域的助力 01远程指导方面 相较于传统视频教学的单向输出模式&#x…...
SpringCloud组件—Eureka
一.背景 1.问题提出 我们在一个父项目下写了两个子项目,需要两个子项目之间相互调用。我们可以发送HTTP请求来获取我们想要的资源,具体实现的方法有很多,可以用HttpURLConnection、HttpClient、Okhttp、 RestTemplate等。 举个例子&#x…...
模型 螃蟹效应
系列文章分享模型,了解更多👉 模型_思维模型目录。个体互钳,团队难行。 1 螃蟹效应的应用 1.1 教育行业—优秀教师遭集体举报 行业背景:某市重点中学推行绩效改革,将班级升学率与教师奖金直接挂钩,打破原…...
符号速率估计——小波变换法
[TOC]符号速率估计——小波变换法 一、原理 1.Haar小波变换 小波变换在信号处理领域被成为数学显微镜,不同于傅里叶变换,小波变换可以观测信号随时间变换的频谱特征,因此,常用于时频分析。 当小波变换前后位置处于同一个码元…...
【架构】ANSI/IEEE 1471-2000标准深度解析:软件密集型系统架构描述推荐实践
引言 在软件工程领域,架构设计是确保系统成功的关键因素之一。随着软件系统日益复杂化,如何有效描述和沟通系统架构成为了一个亟待解决的问题。ANSI/IEEE 1471-2000(正式名称为"推荐软件密集型系统架构描述实践")应运而…...
python兴趣匹配算法
python兴趣匹配算法,用于推荐好友,短视频推荐等等领域 功能列表: 1.用户类的定义,存储用户的基本信息和兴趣。 2.计算两个用户之间兴趣的匹配分数,比较每一位是否相同。 3.根据匹配分数对候选人进行排序。 4.提供交互…...
每日算法-250422
每日算法 - 250422 1561. 你可以获得的最大硬币数目 题目 思路 贪心 解题过程 根据题意,我们想要获得最大的硬币数目。每次选择时,有三堆硬币:最大的一堆会被 Alice 拿走,最小的一堆会被 Bob 拿走,剩下的一堆…...
【MATLAB第116期】基于MATLAB的NBRO-XGBoost的SHAP可解释回归模型(敏感性分析方法)
【MATLAB第116期】基于MATLAB的NBRO-XGBoost的SHAP可解释回归模型(敏感性分析方法) 引言 该文章实现了一个可解释的回归模型,使用NBRO-XGBoost(方法可以替换,但是需要有一定的编程基础)来预测特征输出。该…...
微信公众号消息模板推送没有“详情“按钮?无法点击跳转
踩坑!!!!踩坑!!!!踩坑!!!! 如下 简单说下我的情况,按官方文档传参url了 、但就是看不到查看详情按钮 。如下 真凶&#x…...
WHAT - 静态资源缓存穿透
文章目录 1. 动态哈希命名的基本思路2. 具体实现2.1 Vite/Webpack 配置动态哈希2.2 HTML 文件中动态引用手动引用使用 index.html 模板动态插入 2.3 结合 Cache-Control 避免缓存穿透2.4 适用于多环境的动态策略 总结 在多环境部署中,静态资源缓存穿透是一个常见问题…...
电动单座V型调节阀的“隐形守护者”——阀杆节流套如何解决高流速冲刷难题
电动单座V型调节阀的“隐形守护者”——阀杆节流套如何解决高流速冲刷难题? 在工业自动化控制中,电动单座V型调节阀因其精准的流量调节能力,成为石油、化工等领域的核心设备。然而,长期高流速工况下,阀芯与阀座的冲刷腐…...
