并发编程之 ThreadLocal( 三 )


public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)// this 指的是 ThreadLocal 对象 , value 就是想要设置进去的值map.set(this, value);elsecreateMap(t, value);}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}map.set(this, value); 需要注意的是,这个 map 以及 map 中的 key 和 value 都是保存在 Thread 线程中的,而不是保存在 ThreadLocal 中 。
remove原理跟 get 和 set 类似,这里就不赘述了 。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}ThreadLocal 的内存泄露内存泄漏:当某个对象不再有引用 , 但是所占用的内存不能被回收 。
下面我们来看 ThreadLocal 的静态内部类 ThreadLocalMap ,ThreadLocalMap 的 Entry 其实就是存放每一个ThreadLocal 和 value 键值对的集合 。

并发编程之 ThreadLocal

文章插图

并发编程之 ThreadLocal

文章插图
Entry 静态类的构造方法,分别执行了 super(k); value = https://www.huyubaike.com/biancheng/v; 其中 super(k) 去父类中进行初始化,而从 Entry extends 的父类我们可以看出,WeakReference 父类是一个弱引用类,则说明了 k 值是一个弱引用的, 而 value 就是一个强引用 。
强引用:任何时候都不会被回收,即使发生 GC 的时候也不会被回收(赋值就是一种强引用)
弱引用:对象只被弱引用关联,在下一次 GC 时会被回收 。(可以理解为只要触发一次GC,就可以扫描到并被回收掉)
由此我们可以得知,ThreadLocalMap 的每一个 Entry 都是一个对 key 的弱引用,但是每一个 Entry 都包含了一个对 value 的强引用 。而由于线程池中的线程池存活时间都比较长,那么 Entry 的 key 是可以被回收掉的 , 但是 value 无法被回收,就会发生内存泄漏 。
JDK 的设计者也考虑到了这个不足之处,所以在经常调用的方法,比如 set, remove, rehash 会主动去扫描 key 为 null 的 Entry,并把对应的 value 设置 null , 这样 value 对象也可以被 GC 给回收掉 。
另外在阿里巴巴 Java 开发手册也明确指出,应该显式地调用 remove 方法,删除 Entry 对象 , 避免内存泄漏 。
【强制】 必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响到后续业务逻辑和造成内存泄漏等问题 。尽量在代码中使用 try-finally 块进行回收 。
objThreadLocal.set(someObject);try{ ...} finally { objThreadLocal.remove();}

推荐阅读