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

Java并发编程(2)


ThreadLocal

1、ThreadLocal是什么

  ThreadLocal就是线程本地变量,若创建了一个ThreadLocal变量,那访问这个变量的每个线程都会有这个变量的本地拷贝,但多个线程操作这个变量时,实际是操作自己本地内存里的变量,可以起到线程隔离的作用,避免了线程安全问题。

 

//创建一个ThreadLocal变量localVariable
/ /创建⼀个ThreadLocal变 量
public static ThreadLocal < String > localVariable = new ThreadLocal < > ();//写入:线程可以在任何地方使用localVariable
localVariable.set("xxxx");//读取:线程在任何地方读取的都是它写入的变量
localVariable.get();        //xxxx

2、你在工作中用到过ThreadLocal吗

   用到过,比如在登陆的时候,用户每次访问接口在请求头都会携带一个token,在控制层可以根据这个token,解析出用户的基本信息。由于在后面的服务层、持久层都会用到责怪用户信息,这时候就可以用到ThreadLocal,在控制层拦截请求把用户信息存入ThreadLocal,这样在其他任何地方都可以取出T和read Local中存的用户数据。

  很多其他场景如cookie、session、数据库连接池都可以用ThreadLocal。

3、ThreadLocal怎么实现的

   每个Thread对象里,有一个成员变量ThreadLocal.ThreadLocalMap threadLocals = null; 说明每个线程都有一个属于自己的ThreadLocalMap。当调用threadLocal.set(value) 时,会发生:

  • 先获取当前线程 Thread t = Thread.currentThread();
  • 再拿到该线程ThreadLocalMap
  • 把数据存进去,形式是 <key, value>

  那这里的key和value是什么?value就是set进去的对象。key不是ThreadLocal本身,而是ThreadLocal 的一个 弱引用

  那为什么是弱引用呢?假如key是强引用,若某个ThreadLocal 对象没有外部引用了(ThreadLocal = null),但ThreadLocalMap还持有它,那它就永远不会被GC,造成内存泄露。用了弱引用之后,一旦外部不再持有ThreadLocal,GC就会把它回收。ThreadLocalMap中的key会变成null,只剩下value。JVM之后会清理这些key为null的Entry,避免泄露。

4、ThreadLocal内存泄露是怎么回事

  •  key是弱引用:
    • 外部不再引用ThreadLocal 对象,GC 会回收它。ThreadLocalMap里的entry变成<null, value>,value还在,但程序员无法通过ThreadLocal拿到这份数据。若线程是线程池里的长生命周期线程,这块value会一直留在内存,直到线程结束才可能释放-->内存泄露
  • key是强引用:
    • 即使外部不再引用 ThreadLocal,它也不会被 GC,因为 map 还持有强引用。
    • 弱引用可以减轻泄露风险。
  • 如何避免内存泄露(最佳实践)
try {local.set(new User("Alice"));// 业务逻辑
} finally {local.remove(); // ✅ 主动清理,避免泄漏
}

5、ThreadLocalMap的结构了解吗

   ThreadLocalMap是一个定制化的Map,存放在Thread对象里,每个Thread维护一个自己的ThreadLocalMap,里面的key就是弱引用ThreadLocal。它没有实现Map接口(是内部类,只服务于ThreadLocal),主要是一个Entry[] table数组(每个 Entry 保存 <ThreadLocal弱引用, value>)。

  每次创建新的ThreadLocal对象,都会分配一个threadLocalHashCode值。这个值不是简单的1,2,3...自增,而是每次递增一个特殊的常数0x61c88647。这个数来自黄金分割数(√5 - 1) / 2 ≈ 0.618...。这样可以让哈希值分布更均匀,避免冲突集中。

6、ThreadLocalMap怎么结局hash冲突的

   ThreadLocalMap使用开放定址法,这个坑被人占了就去接着找空着的坑。若插入一个value,通过hash计算后应该落入某个槽位,但这个坑已经被占了,且Entry数据的key和当前不相等,此时会线性向后查找,一直找到为null的槽位才会停止。

  get的时候,也会根据ThreadLocal对象的hash值定位到table中的位置,然后判断该槽位Entry对象中的key是否和get的key一致,若不一致,就判断下一个位置。

7、ThreadLocal扩容机制了解吗

   在ThreadLocalMap.set() 里,若存入元素时发现表里的Entry数量达到阈值(len*2/3),就会触发rehash()。

  • 清理掉已经失效(key=null)的Entry
  • 如果清理后size依然>=3/4 * threshold,就触发resize()扩容。
private void resize() {Entry[] oldTab = table;int oldLen = oldTab.length;int newLen = oldLen * 2;   // 新数组长度翻倍Entry[] newTab = new Entry[newLen];for (int j = 0; j < oldLen; ++j) {Entry e = oldTab[j];if (e != null) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null;  // key 已被回收,帮助 GC} else {// 重新计算哈希位置int h = k.threadLocalHashCode & (newLen - 1);while (newTab[h] != null) {  // 开放地址法,找下一个空位h = nextIndex(h, newLen);}newTab[h] = e; // 放到新数组}}}table = newTab; // 指向新数组
}
  • 新数组翻倍:N->2N,降低负载因子
  • 遍历老数组:把旧数组里的Entrty一个个搬到新数组。若key已经被GC,就清理掉value
  • 重新计算位置:用新数组长度newLen重新取模
  • 冲突处理:若目标格子被占,就调用nextIndex()往后找下一个空位(开放地址法)
  • 更新引用:搬运完毕后,把table指向newTab。

8、父子线程怎么共享数据

  •  普通ThreadLocal不能传递给子线程,因为ThreadLocal的值存放在当前对象的ThreadLocals变量里,就算是父线程,也不算是同一个线程。
  • 解决办法:在Thread类里除了threadLocals之外,还有一个:ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 关键点在于子线程初始化时,从父线程的InheritableThreadLocalMap拷贝了一份数据。
public class InheritableThreadLocalTest {public static void main(String[] args) {ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();// 父线程设置值threadLocal.set("父线程的值");// 子线程new Thread(() -> {System.out.println("子线程获取:" + threadLocal.get());}).start();}
}

//子线程获取:父线程的值
  • 限制:只是在创建子线程那一刻复制,后续修改不同步。
  • 线程池问题:线程池里的线程是复用的,子线程不会每次都重新init(),所以默认的InheritableThreadLocal在线程池场景可能会出问题。为解决这个,阿里开源了TransmittableThreadLocal (TTL),专门用于线程池下传递上下文。

 

参考

[1] 沉默王二公众号

http://www.wxhsa.cn/company.asp?id=3828

相关文章:

  • 完整教程:WebApp 的价值与实现:从浏览器架构到用户体验优化
  • Ubuntu 安装百度网盘
  • 八字喜用神起名大师 API 接口
  • 在CentOS 7上集成cJSON库的方法
  • 作业1
  • 网站截图与 HTML 快照 API 接口
  • 深入解析:精确位置定位,AR交互助力高效作业流程​
  • sdjaivkdshwqeofhsoejbc dfb vnhgtbv
  • 开篇自我介绍随笔
  • 第八周
  • Tita 项目一体化管理:驱动项目全周期高效运营的引擎
  • 飞行 NED坐标系(北东地坐标系):
  • windows与linux环境下网络编程
  • 在飞牛系统中通过docker形式部署Nginx proxy manager
  • Es索引同步异步Canal解耦方案
  • 在Ubuntu上配置phpMyAdmin和WordPress环境
  • “四人过河”经典问题
  • 完整教程:C#语言入门详解(18)传值、输出、引用、数组、具名、可选参数、扩展方法
  • DevOps On Kubernetes
  • 深耕Linux系统的道与术
  • Debugging via Intel DCI 小蓝盒
  • 我做了个 AI 文档阅读神器,免费开源!
  • 20250913 P11503 [NordicOI 2018] Nordic Camping
  • Dify实战训练营(基础班)(全免费值得收藏)
  • C 语言的历史和版本
  • PostgreSQL 上的向量搜索实践
  • 【数据结构——图与邻接矩阵】 - 实践
  • (读书笔记)平衡掌控者
  • 带头结点的单链表删除指定位置结点
  • 《文字、语言与数字的奇妙联结》读后感,大公司内部编码规范,本学期编码遵守规范