重温 JAVA -- ThreadLocal 终

心无私天地宽 2020-11-08 23:46:29
java ThreadLocal SegmentFault 重温


ThreadLocal 是什么

作用

ThreadLocal 用于存储线程间的私有变量

数据结构

image.png

内存泄露?

要解释这个问题之前,需要先看 JAVA 对象中的 强引用、软引用、弱引用、虚引用

对象的四种引用类型

  • 强引用
    new 或通过反射创建出来的对象被称为强引用,只要强引用还存在,就不会被垃圾回收
  • 软引用
    使用 SoftReference 修饰的对象被称为软引用,当内存不足时,软引用对象会被回收
  • 弱引用
    使用 WeakReference 修饰的对象被称为弱引用,当对象只有弱引用时,GC 时,该对象会被回收
  • 虚引用
    使用 PhantomReference 修饰的对象被称为虚引用,当对象被回收时会收到系统通知

WeakReference 案例介绍

public class WeakReferenceObj extends WeakReference<Object> {
public WeakReferenceObj(Object referent) {
super(referent);
}
public static void main(String[] args) {
WeakReferenceObj weak = new WeakReferenceObj(new Object());
int i = 0;
while(true){
if((weak.get()) != null){
i++;
System.out.println("Object is alive for "+i+" loops - "+weak);
}else{
System.out.println("Object has been collected.");
break;
}
}
}
}

当以上程序运行了一段时间后,WeakReference 指向的对象就会只因被弱引用引用,而将对象回收
image.png

若将上诉代码改造为下面的代码

public class WeakReferenceObj extends WeakReference<Object> {
public WeakReferenceObj(Object referent) {
super(referent);
}
public static void main(String[] args) {
Object o = new Object();
WeakReferenceObj weak = new WeakReferenceObj(o);
int i = 0;
while(true){
System.out.println(o);
if((weak.get()) != null){
i++;
System.out.println("Object is alive for "+i+" loops - "+weak);
}else{
System.out.println("Object has been collected.");
break;
}
}
}
}

你会发现,不管运行多久,弱引用指向的对象都不会被回收。因为此时的 o 还被一个强引用指向。即 打印流

ThreadLocal 中的内存泄露

ThreadLocal 做为弱引用存在于 ThreadLocalMap key 中。因为是弱引用,当 ThreadLocal 只被弱引用指向时,在触发 GC 后,ThreadLocal 会被回收,即 ThreadLocalMap key 会为 nullvalueThreadLocalMap 强引用指向,导致 value 无法被回收。ThreadLocalMap 又是 Thread 的一个属性,因此除非 Thread 销毁,ThreadLocalMap 才会被释放,这样一来,Entry 不为 null ,key = null, value 又有值(占着茅坑不拉屎),ThreadLocalMap 如果没有有效的 清理 Entry 不为 null, key = null 的机制,那么就会因为 value 无法被回收,从而导致内存泄露。

ThreadLocal 清理机制

ThreadLocal 内存泄露的分析中,我们知道,如果 ThreadLocal 没有有效的清理机制,那么必然会导致内存泄露。那么接下来将介绍 ThreadLocal2 种清理机制,防止内存泄露

探测式清理

代码的逻辑在:ThreadLocalMap.expungeStaleEntry
key = null 的位置向前清理,然后遍历 ThreadLocalMap 直到 Entry != null。如果遇到 key = null 则将 Entry、value 设置为 null,如果 key != null, 则重新 hash 重新将该 Entry 放入 ThreadLocalMap 中。

ThreadLocalget(), set(T t),从何处开始清理不大一样,但是最终都是调用 expungeStaleEntry 方法,进行清理。

get()

清理点为:在从 x 下标 获取不到对应 keyvalue 时,会从 x 下标开始清理

set(T t)

清理点为:如果在设置值时,发现在 x 下标 key = null。则会从 x 往前查找 key = null,直到 Entry = null,如果查找到, x 会被赋予刚才元素的下标。最后再从 x 处开始清理。

启发式清理

代码的逻辑在:ThreadLocalMap.cleanSomeSlots

i 位置开始,直到当前 ThreadLocalMapEntry 个数 n >>> 1 != 0
如果 Entry != null,但 key = null, 会调用 expungeStaleEntry 进行清理。

如何预防

使用完毕后,调用 remove 方法,进行清理。

结论

get、set 方法在内部均会对过期 key 进行清理。但是为了以防万一,在使用完毕后,还需要手动调用 remove 方法进行清理

ThreadLocal Hash 算法

ThreadLocal 中有个属性 HASH_INCREMENT = 0x61c88647,它被称为 黄金分割数hash 增量为该数字,因此,产生的 hash 数值非常的均匀。

private static final int HASH_INCREMENT = 0x61c88647;
public static void main(String[] args) {
for (int i = 0; i < 16; i++) {
int hash = HASH_INCREMENT * i + HASH_INCREMENT;
System.out.print(hash & (16 - 1));
System.out.print(",");
}
}

生成的结果如下:

7,14,5,12,3,10,1,8,15,6,13,4,11,2,9,0,

ThreadLocal Hash 冲突

ThreadLocal Hash 冲突使用的是 线性探测再散列的开放寻址法
所谓线性探测算法如下:从当前发生冲突的位置,往后查找,直到找到一个 null 的位置插入。

扩容

当进行 set 后,会执行 cleanSomeSlots 如果有清理元素,且数组大小达到数组扩容阈值 thresholdlen * 2 / 3)则会进行探测式清理。如果清理完毕后,数组大小大于 treshold * 3 / 4 则进行扩容。

扩容时,数组变为原来的 2 倍,且将整个 ThreadLocalMapkey 重新 hash 放入 table

灵魂拷问,为什么 ThreadLocalMap key 是弱引用?

key 是强引用

ThreadLocalMap 的生命周期与 Thread 一致。如果 Thread 存活太久,添加了非常多的 ThreadLocal。此时若在代码中将 ThreadLocal 设置为 null,理应被回收。但是,因为 ThreadLocalMap 还存在 ThreadLocal 的强引用,而导致无法被回收,从而导致内存泄露。并且在代码里面,很难判断 ThreadLocal 在别的地方还有没有引用。

key 是弱引用

ThreadLocal 是弱引用,代码中被设置为 null 后,因为只存在弱引用,所以,在 GC 后会被正常回收。但是 key = null 也会存在 value 内存泄露。虽然 value 会存在内存泄露,但是可以通过判断 key = null 来判断,ThreadLocal 已没有其他引用。

结论

个人认为最核心的原因是:ThreadLocalMap 的生命周期与 Thread 一致。太过难于判断ThreadLocal 只在 ThreadLocalMap 中有引用。因此设置为弱引用,让 GC 回收 ThreadLocal 后,用 null 来判断

参考

面试官:听说你看过ThreadLocal源码?我来瞅瞅?

版权声明
本文为[心无私天地宽]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000037765404

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云