ThreadLocal弱引用被垃圾回收的话保存的值岂不是取不到了?

先说结论:我们new出来的theadlocal对象存放了值之后,虽然里面的key是弱引用,但是这个外面new出来的threadlocal对象是强引用,此时不会将这个key回收

  • ThreadLocal是用自己作为key,不是线程作为key,线程类有一个类型为ThreadLocalMap的属性,每个线程包含了独有的ThreadLocalMap然后用ThreadLocalkey去获取value,线程之间能隔离获取就是因为ThreadLocalMap是隔离的,每个线程有独立的ThreadLocalMap

    • image-20230328093151675

    • ThreadLocal有一个内部类ThreadLocalMap,ThreadLocalMap有一个内部类Entry,这Entry相当于键值对,里面的键值对是<ThreadLocal k,Object v>

    • image-20230328092710288

    • image-20230328092721104

    • ThreadLocalMap里面有一个 Entry数组threadlocal.set(value)的时候会用当前的ThreadLocal对象作为key计算哈希值,和数组长度取余得到一个数组下标i,然后构建Entry对象存入i位置

      • tab[i] = new Entry(key, value);
        
      • 先不考虑哈希碰撞,下面说

  • 每次用threadlcoal.get()的时候先获取当前线程的这个ThreadLocalMap,然后用当前的ThreadLocal对象作为key计算在Entry数组中取出Entry对象e,然后e.value获取值

    • 由于Entry这个键值对中的key是弱引用,垃圾回收会回收key,但是Entry对象还在,value值也在,key没了,也就是说此时即使key被垃圾回收了但是Entryvalue还在此时如果能得到这个Entry对象,就仍然可以得到它的value

    • public class Test {
          public static void main(String[] args) throws Exception {
              Thread thread = Thread.currentThread();
              //得到线程私有的属性threadLocals,它是ThreadLocal.ThreadLocalMap对象
              Field field = thread.getClass().getDeclaredField("threadLocals");
              field.setAccessible(true);//由于不是public属性,爆破
              //这里不能用ThreadLocal.ThreadLocalMap指向,因为ThreadLocalMap不是public得不到
              Object o1 = field.get(thread);
              //得到ThreadLocalMap的table属性,他是一个Entry数组,
              Field table = o1.getClass().getDeclaredField("table");
              table.setAccessible(true);
              //得到Entry数组,数组是支持向上转型的
              Object[] o2 = (Object[])table.get(o1);
      
              ThreadLocal<MyObject> local = new ThreadLocal<>();
              local.set(new MyObject());
              //得到ThreadLocal计算的哈希值字段,用于计算数组下标
              Field field1 = local.getClass().getDeclaredField("threadLocalHashCode");
              field1.setAccessible(true);
              int hashcode = (int)field1.get(local);
      
              System.out.println(local.get());//输出
      
              //触发垃圾回收
              local = null;
              System.gc();
              Thread.sleep(1000);
      
              //根据哈希值计算数组下标,得到Entry,然后获取它的value属性
              Object o = o2[hashcode & (o2.length - 1)];
              Field field2 = o.getClass().getDeclaredField("value");
              field2.setAccessible(true);
              Object value = field2.get(o);
      
              System.out.println(value);//最后输出
          }
      }
      class MyObject {
          @Override
          public String toString() {
              return "得到该对象";
          }
      }
      
    • image-20230328104101366

      • 由此可见即使Entry中的threadlocal用了弱引用,被回收了也还是能得到value
    • 注意,虽然ThreadLocal对象是弱引用,但是上面触发垃圾回收仍然需要先将local=null,否则无法回收。因为此时Entry中的ThreadLocal虽然是弱引用,但是外面这个传入的ThreadLocal对象是强引用,而且这两个对象是同一个对象,此时不会回收

      • ps:垃圾回收器判断是否回收一个对象,用的是可达性分析法,从GC ROOT对象出发遍历所有被涉及的对象,没被遍历到的视为不可达,就当垃圾进行回收,如果一个对象仅仅有一个弱引用可达,也会被回收

        • 此时我们的ThreadLocal对象再栈中有强引用指向,因此即使Entry中是弱引用也不会回收
          • 证明:将local=null注掉,断点分析查看Entry数组中的引用是否还在
            • image-20230328105037664
            • 可以看到entry中referent字段仍有threadlocal的引用,不会回收
              • image-20230328105312990
            • 取消注释,发现已经被回收,referent=null
              • image-20230328105347167
  • 那么问题来了,既然有没有ThreadLocal对象都能获取value,那为什么还要存储它,只存value不就行了?

    • 原因是在调用ThreadLocal对象的set()方法的时候,可能发生哈希碰撞

      • 此时我们需要判断:这个哈希碰撞是同一个ThreadLocal对象产生的还是不同的ThreadLocal对象得到了同样的哈希值

        1. 如果使用一个ThreadLocal对象,就覆盖原先的value
        2. 如果是不同的,就用开放定址法解决冲突
        private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
        	//nextIndex就是返回下一个下标,如果此时是最后一个就返回0,开放定址法
            for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
        		//使用一个对象就覆盖
                if (k == key) {
                    e.value = value;
                    return;
                }
        		//entry不为空k为空,表示之前的entry弱引用被回收,另外处理
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
        
            //找到空位置
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ThreadLocal并不是弱引用的主要原因是为了避免内存泄漏问题。如果ThreadLocal弱引用,那么在外界对ThreadLocal的强引用回收后,ThreadLocal可能会被垃圾回收,但是与之对应的变量副本(即ThreadLocalMap中的value)还存在,并且无法被及时清理和回收,从而导致内存泄漏。为了避免这个问题,ThreadLocal使用了弱引用来解决。弱引用指向的对象在下次垃圾回收时会被回收,这样在ThreadLocal没有强引用指向时,ThreadLocalMap中的对应Entry的key也会被回收,从而确保变量副本也可以被及时清理和回收,避免内存泄漏的发生。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [关于ThreadLocal为什么采用弱引用的理解](https://blog.csdn.net/m0_49908016/article/details/120497770)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [ThreadLocal,你真的了解吗?](https://download.csdn.net/download/weixin_38607864/13742056)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一酒。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值