车驰夜幕|面试时被问到ThreadLocal别慌,你要的答案都在这里( 五 )


为什么要做这样的清除?
我们知道entry对象里面包含了threadLocal和value , threadLocal是WeakReference(弱引用)的referent 。 每次垃圾回收期触发GC的时候 , 都会回收WeakReference的referent , 会将referent设置为null 。 那么table数组中就会存在很多threadLocal = null 但是 value不为空的entry , 这种entry的存在是没有任何实际价值的 。 这种数据通过getEntry是获取不到值 , 因为它里面有if (e != null && e.get() == key)这句判断 。
为什么要使用WeakReference(弱引用)?
如果使用强引用 , ThreadLocal在用户进程不再被引用 , 但是只要线程不结束 , 在ThreadLocalMap中就还存在引用 , 无法被GC回收 , 会导致内存泄漏 。 如果用户线程耗时非常长 , 这个问题尤为明显 。
另外在使用线程池技术的时候 , 由于线程不会被销毁 , 回收之后 , 下一次又会被重复利用 , 会导致ThreadLocal无法被释放 , 最终也会导致内存泄露问题 。
4.ThreadLocal有哪些坑
内存泄露问题:
ThreadLocal即使使用了WeakReference(弱引用)也可能会存在内存泄露问题 , 因为 entry对象中只把key(即threadLocal对象)设置成了弱引用 , 但是value值没有 。 还是会存在下面的强依赖:
Thread -> ThreaLocalMap -> Entry -> value要解决这个问题就需要调用get()、set(T value) 或 remove()方法 。 但是 get()和set(T value) 方法是基于垃圾回收器把key回收之后的基础之上触发的数据清理 。 如果出现垃圾回收器回收不及时的情况 , 也一样有问题 。
所以 , 最保险的做法是在使用完threadLocal之后 , 手动调用一下remove方法 , 从源码可以看到 , 该方法会把entry中的key(即threadLocal对象)和value一起清空 。
线程安全问题:
可能有些朋友认为使用了threadLocal就不会出现线程安全问题了 , 其实是不对的 。 假如我们定义了一个static的变量count , 多线程的情况下 , threadLocal中的value需要修改并设置count的值 , 它一样有问题 。 因为static的变量是多个线程共享的 , 不会再单独保存副本 。
5.总结
【车驰夜幕|面试时被问到ThreadLocal别慌,你要的答案都在这里】1.每个线程都有一个threadLocalMap对象 , 每个threadLocalMap里面都包含了一个entry数组 , 而entry是由key(即threadLocal)和value(数据)组成 。
2.entry的key是弱引用 , 可以被垃圾回收器回收 。
3.threadLocal最常用的这四个方法:get() ,initialValue() , set(T value) 和 remove() , 除了initialValue方法 , 其他的方法都会调用expungeStaleEntry方法做key==null的数据清理工作 。
4.threadLocal可能存在内存泄露和线程安全问题 , 使用完之后 , 要手动调用remove方法 。
5.其实实际项目中使用threadLocal的机会还是挺多的 , 比如有些参数 , 你在多个方法中都可能会使用 , 但又不想在多个方法直接层层传递 , 这个时候就可以使用threadLocal 。 最典型使用threadLocal的场景是事务 和 MDC日志功能 。
朋友们如果喜欢这篇文章的话 , 请关注一下我的公众账号 :苏三说技术 , 后面会有很多干货分享 , 谢谢大家 。