ThreadLocal原理以及其安全问题

ThreadLocal原理以及其安全问题,第1张

ThreadLocal原理以及其安全问题

ThreadLocal 是一个线程内共享数据的类,其原理是在线程有一个 ThreadLocalMap,key是ThreadLocal对象,value是自定义的数据,所以在同一个线程中,用同一个threadlocal去get数据,能取到同样的数据。实现线程内数据共享。

ThreadLocalMap


说白了 ThreadLocalMap 就是有一个 Entry 的数组, Entry就是一个有着两个成员变量的类。一个变量是 value , 另一个成员变量 在继承的 WeakReference 里面,是 referent ,当GC的时候这个成员变量就会被释放

threadLocal 用法很简单,就是set 和get不做过多赘述。我们来看看为什么ThreadLocalMap的key要做成 WeakReference .
我们先来看如下的引用关系:


如果entry的那个弱引用改成强引用的话,那么在很多情况下, 线程的生命周期和程序是相同的,那么就算用户 threadLocal = null; 想要释放threadLocal的内存地址,也没用。造成内存泄漏。所以设计成弱引用的时候,当用户想要释放threadLocal 资源的时候(虽然很少) threadLocal = null; 线程的弱引用也会在下次GC的时候回收。
那么问题来了,为什么value不也设计成弱引用呢?
因为最根本的原因是因为 key 和 value 的生命周期是不一样的。 key 和 value是两个代码创建出来的,他们并不能保证一起消失,所以当 value如果设计成弱引用的时候,可能value指向消失了,但是key的强引用还存在,那么就可能出现key存在但是get不到东西的情况。

内存泄漏问题

上面说了,为了保证key一定能get到value的情况。在线程中强引用了value,那么这个value的生命周期就和该线程相同了。造成了内存泄漏。 就算在ThreadLocalMap里面的添加方法的时候会去删除key为空的value
条件是

  1. hash冲突会判空删除
  2. 插入的下标往后遍历查找为空的数据删除
        private void set(ThreadLocal key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }
               
                if (k == null) {
                # 在hash冲突的时候, 如果上一个key为空,就把value给删了
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            # 插入当前的地址往后遍历一波,查看有没有value为空的数据
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

即使是有这样的判断,如果key在数据之前,并且没有hash碰撞,那么value也永远都删除不掉。
所以在用完value之后一定要主动调用remove让线程删除引用, remove方法最终调用如下

数据错误问题

有的人可能觉得,自己的threadLocal是静态变量,生命周期本来就跟项目相同,并且一个项目中线程几十个存放的也都是一些用户数据,没有太多的泄漏。那么ThreadLocal就还会因此引发第二个问题,数据错误。
现在项目中大部分都采用线程池技术,Tomcat,Apache 等等。
如果在这一次请求中存入了用户的数据没有释放,在其他用户访问的时候可能复用了当前线程,然后取出来的还是上一个用户的数据

ThreadLocal 线程共享

Thread 类里面还有一个成员变量 inheritableThreadLocals 可以用来做父子线程的数据共享,原理很简单,就是在创建线程的时候判断父线程的这个变量有没有值,如果有值,则也拷贝一份到自身。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5697532.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存