ThreadLocal 原理与线程上下文传递

ThreadLocal 是什么 ThreadLocal 用于保存线程私有变量。 同一个 ThreadLocal 可以被多个线程访问,但每个线程读取和修改的都是自己保存的值,线程之间默认互不影响。 常见用途包括: 保存当前用户信息; 保存 TraceId; 保存租户或请求上下文; 在线程内部隐式传递参数。 private static final ThreadLocal<String> USER_CONTEXT = new ThreadLocal<>(); 调用 set() 时,数据并不是直接存入 ThreadLocal 对象,而是存入当前线程内部的 ThreadLocalMap: Thread └── ThreadLocalMap ├── ThreadLocal A → Value A └── ThreadLocal B → Value B 一个线程可以使用多个 ThreadLocal,但这些数据都保存在当前线程自己的 ThreadLocalMap 中。 ThreadLocal 为什么会内存泄漏 ThreadLocalMap 中的 Entry 可以简化为: static class Entry extends WeakReference<ThreadLocal<?>> { Object value; } 其中: Key,也就是 ThreadLocal,是弱引用; Value 是强引用。 如果外部不再引用某个 ThreadLocal,GC 可以回收 Key,此时 Map 中可能留下: ...

2025年4月2日 · 3 分钟

Redis 大 key 问题

大 key 标准 在一般的业务场景下(并发和容量要求都不大): 单个string的value>1MB 容器ds元素数量超过10000 在高并发且低延迟的场景中:(互联网上看到比较多的版本) 单个string的value>10KB 容器ds元素数量超过5000或整体value>10MB 阿里云的云版本Redis: Key本身的数据量过大:一个String类型的Key,它的值为5MB Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10000个 Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1000个但这些成员的Value(值)总大小为100MB 但其实Redis大key问题的定义及评判准则并非一成不变,没有固定的判别标准,还是要根据Redis在实际业务中的使用来综合评估。(注意边界问题) 大 key 影响 读取成本高 时延(执行命令时间更长) 带宽(大 key 消耗更多的带宽,从而影响相关服务) 大key的写操作容易阻塞,从而导致无法正常响应 慢查询 主从同步异常 占更多的存储空间,从而导致逐出,甚至是 OOM(Out Of Memory) 集群架构下,某个数据分片的内存使用率远超其他数据分片,即数据分片的内存资源不均衡 根本原因 Redis 是单线程! 大 key 产生原因 业务设计不合理。这是最常见的原因,不经过合理拆分,就直接把大json塞在一个key中;甚至塞二进制文件数据。 没有处理好value的动态增长问题。如果一直添加value数据,没有定期的删除机制、合理的过期机制或者卡量,大key只是早晚的问题。(例如:微博明星的粉丝列表、热门评论、直播弹幕等) 程序bug。某些异常情况导致某些key的生命周期超出预期,或者value数量异常增长。比如LIST的业务消费侧发生代码故障,造成对应Key的成员只增不减。 找到大 key bigkeys 参数 参考官方文档:https://redis.io/docs/latest/develop/connect/cli/#scanning-for-big-keys 使用redis-cli命令客户端,连接Redis服务的时候,加上 --bigkeys 参数,可以以遍历的方式分析Redis实例中的所有Key,并返回Key的整体统计信息与每个数据类型中Top1的大Key。(原理是基于SCAN) $ redis-cli --bigkeys # Scanning the entire keyspace to find biggest keys as well as # average sizes per key type. You can use -i 0.01 to sleep 0.01 sec # per SCAN command (not usually needed). [00.00%] Biggest string found so far 'key-419' with 3 bytes [05.14%] Biggest list found so far 'mylist' with 100004 items [35.77%] Biggest string found so far 'counter:__rand_int__' with 6 bytes [73.91%] Biggest hash found so far 'myobject' with 3 fields -------- summary ------- Sampled 506 keys in the keyspace! Total key length in bytes is 3452 (avg len 6.82) Biggest string found 'counter:__rand_int__' has 6 bytes Biggest list found 'mylist' has 100004 items Biggest hash found 'myobject' has 3 fields 504 strings with 1403 bytes (99.60% of keys, avg size 2.78) 1 lists with 100004 items (00.20% of keys, avg size 100004.00) 0 sets with 0 members (00.00% of keys, avg size 0.00) 1 hashs with 3 fields (00.20% of keys, avg size 3.00) 0 zsets with 0 members (00.00% of keys, avg size 0.00) 总结 优点:方便、快速、安全。 缺点:分析结果不可定制化,准确性与时效性差。 Redis RDB Tools工具 使用支持定制化分析的开源工具Redis RDB Tools,分析RDB文件,扫描出Redis大key。可以根据自己的精细化需求,全面地分析Redis实例中所有Key的内存占用情况,同时也支持灵活地分析查询。 ...

2025年3月15日 · 2 分钟

《深入拆解 Tomcat & Jetty》读书笔记

2025年2月14日 · 28 分钟

组成原理与汇编学习笔记整理

第一章 绪论 1.2 计算机的软硬件组成 1.3 层次结构模型 第二章 计算机中的信息表示 2.1 数值型数据的表示 一、原码、补码、反码、移码的定义 原码 原码是计算机对数字的最直接的二进制表示。 符号位:最高位为符号位,0表示正数,1表示负数。 数值位:剩余位表示数值的绝对值。 优点:直观,便于理解。 缺点:不便于进行加减运算,尤其是涉及正负数时。 例如: +5+5:00000101 (8位) −5-5:10000101 (8位) 反码 正数的反码与原码相同。 负数的反码是对原码的数值位逐位取反(符号位不变)。 用于解决原码加减法中符号位处理的问题。 例如: +5+5:00000101 (反码与原码相同) −5-5:11111010 (符号位为1,数值位取反) 补码 正数的补码与原码相同。 负数的补码是在反码的基础上加1。 补码广泛用于计算机内部的加减运算,因为它统一了正负数的运算规则。 例如: +5+5:00000101 (补码与原码相同) −5-5:11111011 (反码11111010加1) 移码 移码是一种特殊的编码方式,常用于浮点数表示中的指数部分。 计算方式:移码 = 真值 + 偏移量(通常为 2n−12^{n-1},nn为位数)。 移码的好处是将所有的值都转换为非负数,方便比较大小。 例如:假设8位移码,偏移量为127: +5+5:真值5,移码为127 + 5 = 132,二进制10000100。 −5-5:真值-5,移码为127 - 5 = 122,二进制01111010。 二、正数、负数、定点数、浮点数、小数的表示 正数与负数的表示 原码:符号位 + 数值位(正数符号位为0,负数符号位为1)。 补码:统一正负数,正数直接使用原码,负数需要补码处理。 反码:正数直接使用原码,负数需按反码规则处理。 三、例子说明:定点数、浮点数、小数的表示 三、总结 编码方式 正数表示 负数表示 特点 原码 符号位 + 数值位 符号位 + 数值位 直观,但不适合运算 反码 与原码相同 符号位不变,数值位逐位取反 改善运算规则,但仍需特殊处理加减运算 补码 与原码相同 反码 + 1 统一运算规则,便于硬件实现 移码 真值 + 偏移量 真值 + 偏移量 主要用于浮点数表示,所有值转换为非负数 表格对比总结 数值 表示方式 二进制形式 说明 5.755.75 定点数 00010111 小数点固定在最后 5.755.75 浮点数(IEEE 754) 0 10000001 有错 −0.625-0.625 定点数 10000010 符号位+数值位 −0.625-0.625 浮点数(IEEE 754) 1 01111110 有错 2.2 字符的表示 ASCII(American Standard Code for Information Interchange) 概念: ASCII 是一种早期的字符编码标准,用于表示英文字符和控制符号。它为字符分配了数字编号,主要用于简化计算机和设备之间的文本交换。 ...

2024年12月7日 · 4 分钟

布隆过滤器

布隆过滤器主要是用于检查在于不在的一个东西。 布隆过滤器判断在,可能不在。 判断不在,一定不在。所以也有一定的风险。而且不能删去元素,除非重新做一遍。 为解决单个布隆过滤器过大、查询压力集中,以及扩容不方便的问题,可以将数据分片,分别存入多个布隆过滤器。只要各分片的位数与元素数量之比保持不变,理论误判率就基本不变。 布隆过滤器的理论误判率近似为: p≈(1−e−kn/m)k p \approx \left(1-e^{-kn/m}\right)^k p≈(1−e−kn/m)k其中,mmm 是位数组的总位数,nnn 是插入的元素数量,kkk 是哈希函数数量。最优哈希函数数量约为: kopt=mnln⁡2 k_{\mathrm{opt}}=\frac{m}{n}\ln 2 kopt​=nm​ln2假设原过滤器使用 1000 万位,存储 100 万个元素,则每个元素对应的位数为: mn=107106=10 \frac{m}{n}=\frac{10^7}{10^6}=10 nm​=106107​=10将其均匀拆成两个分片后,每个分片使用 500 万位,存储约 50 万个元素: ...

2024年11月24日 · 1 分钟

Lua

import org.redisson.api.RedissonClient; import org.redisson.api.RScript; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class LuaScriptService { @Autowired private RedissonClient redissonClient; public Long executeLuaScript(String key, Long incrementValue) { // 获取 Lua 脚本 RScript script = redissonClient.getScript(); // 执行 Lua 脚本,返回结果 return script.eval(RScript.Mode.READ_WRITE, "if redis.call('exists', KEYS[1]) == 1 then return redis.call('incrby', KEYS[1], ARGV[1]) else redis.call('set', KEYS[1], ARGV[1]) return ARGV[1] end", RScript.ReturnType.INTEGER, java.util.Collections.singletonList(key), // KEYS[1] java.util.Collections.singletonList(String.valueOf(incrementValue)) // ARGV[1] ); } } 参数说明: RScript.Mode.READ_WRITE:表示该脚本会修改 Redis 数据,因此我们选择 READ_WRITE 模式。 eval() 方法的参数: Lua 脚本内容:要执行的 Lua 脚本字符串。 RScript.ReturnType.INTEGER:表示返回值为整数类型。可以根据你的脚本返回类型选择其他类型(如 STRING、BOOLEAN、VOID 等)。 KEYS:传入的 Redis 键列表。通过 java.util.Collections.singletonList(key) 传递。 ARGV:传入的参数列表,表示脚本的输入参数。通过 java.util.Collections.singletonList(String.valueOf(incrementValue)) 传递。

2024年11月8日 · 1 分钟

Java线程池深度解析

Java线程池深度解析 目标 深入理解Java线程池的底层实现原理、源码设计和最佳实践,掌握线程池的性能调优和问题排查方法。 核心架构 ThreadPoolExecutor整体架构 ThreadPoolExecutor是Java并发包中的核心组件,实现了ExecutorService接口,通过池化技术复用线程,避免频繁创建和销毁线程的开销。 线程池执行流程 底层实现原理 1. 状态管理机制 线程池使用一个AtomicInteger类型的ctl变量同时维护两个关键信息: 运行状态(runState):高3位存储 工作线程数(workerCount):低29位存储 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 运行状态常量 private static final int RUNNING = -1 << COUNT_BITS; // 11100000000000000000000000000000 private static final int SHUTDOWN = 0 << COUNT_BITS; // 00000000000000000000000000000000 private static final int STOP = 1 << COUNT_BITS; // 00100000000000000000000000000000 private static final int TIDYING = 2 << COUNT_BITS; // 01000000000000000000000000000000 private static final int TERMINATED = 3 << COUNT_BITS; // 01100000000000000000000000000000 // 位运算工具方法 private static int runStateOf(int c) { return c & ~CAPACITY; } // 获取运行状态 private static int workerCountOf(int c) { return c & CAPACITY; } // 获取工作线程数 private static int ctlOf(int rs, int wc) { return rs | wc; } // 组合状态和线程数 设计优势: ...

2024年9月21日 · 6 分钟

开发过程中碰到的Lombok问题

Lombok Lombok 和 Jackson 矛盾错误 @getter,@setter在遇到形如aName的时候,jakson无法将其正确反序列化 当 RESTful 接口接收到客户端发送的 JSON 请求体 时,Spring Boot 使用 Jackson 将 JSON 数据反序列化为 Java 对象。 得出结论Lombok在转换的时候将其转换为AName,符合直觉,但是jakson无法识别。 示范如下: 用Lombok实现getter,setter等配置的自动注入: @Data public class User { private String name; private String aName; private String aaName; } 发现get,set方法正确实现,符合直觉 @Generated public void setName(final String name) { this.name = name; } @Generated public void setAName(final String aName) { this.aName = aName; } @Generated public void setAaName(final String aaName) { this.aaName = aaName; } json请求字段 ...

2024年5月9日 · 2 分钟