并发编程之 ThreadLocal( 三 )



并发编程之 ThreadLocal

文章插图
如果没有重写 initialValue 时,默认会返回 null
并发编程之 ThreadLocal

文章插图
如果线程先前调用了set方法,在这种情况下,不会为线程调用本 initialValue 方法,而是直接用之前 set 进去的值 。
在通常情况下,每个线程最多只能调用一次 initialValue 方法,但是如果已经调用了 remove 方法之后,再调用 get 方法,则可以再次调用 initialValue 方法 。
getget 方法是先取出当前线程的 ThreadLocalMap,然后调用 map.getEntry 方法,把本 ThreadLocal 的引用作为参数传入,取出 map 中属于本 ThreadLocal 的value 。
public T get() {// 获取当前线程Thread t = Thread.currentThread();// 获取当前线程的threadLocals 成员变量ThreadLocalMap map = getMap(t);if (map != null) {// this 指的是 ThreadLocal 对象,通过 map.getEntry 来获取我们通过 set 方法设置进去的 value 值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}set跟 get 一样,同样是先获取当前线程的引用,然后再获取当前线程的 threadLocals 成员变量,如果 threadLocals 为null,即还未初始化,就会执行 createMap 方法来进行初始化 。
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 给回收掉 。

经验总结扩展阅读