ThreadLocal学习

ThreadLocal是什么

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景

ThreadLoacl数据结构

ThreadLocal学习_第1张图片
image.png

通过上图可以看出

  • 每一个线程都维护这一个ThreadLocalMap的数组,这个就是实现线程之间互不影响的原因,因为每个线程自己维护了一个Entry。
  • ThreadLocal只是用来管理每个线程中Entry的一个工具,因为真正的ThreadLocalMap是定义在每一个线程中,可以通过ThreadLocal的get,set方法明白这一切
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
##通过Thread.currentThread获取到当前线程
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
#设置时候,ThreadLocalMap的key值就是threadLocal,所以后面可以通过ThreadLocal获取到这个entry的value值
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
 }

  • 为啥是一个Entry数组?因为可能你定义了多个ThreadLocal变量,每个ThreadLocal指向一个Entry,这样不同的ThreadLocal可以操作不同的数据
public class ThreadLocalTest {
    static ThreadLocal LOCAL = new ThreadLocal();
    static ThreadLocal LOCAL2 = new ThreadLocal();
}
  • ThreadLocalMap如何解决Hash冲突?ThreadLocalMap是定义再ThreadLocal类中的一个数据结构,他采用的是线性探测的方式而非像HashMap采用链表的模式。所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。基于这特点,建议最好Entry[]的数量不要太多,所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能
  • 从结构图可以看出,用ThreadLocal时候set进去的对象必须是每个线程单独new出来的,例如先new了一个对象X,然后后面多线程里面set(X),这样其实每一个线程中Entry的Value指向的是同一个对象X,这样会有问题

ThreadLocal内存泄露

ThreadLocal学习_第2张图片
image.png

为什么会内存泄漏?

ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的Key引用的,因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏
从上可以看出导致内存泄露的几个原因:

  • 使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏
  • 分配使用了ThreadLocal又不再调用get()、set()、remove()方法,那么就会导致内存泄漏
  • 由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key的value就会导致内存泄漏,而不是因为弱引用

解决方法

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。如果一个线程执行时间过长,代码上可以将threadlocal用完后马上remove掉,防止后面代码执行过程导致value值一直得不到释放

参考

https://www.jianshu.com/p/98b68c97df9b

你可能感兴趣的:(ThreadLocal学习)