ThreadLocal的一些理解

本文详细解析了ThreadLocal的作用,包括解决并发安全问题的方法,以及源码中的ThreadLocalMap结构。同时探讨了ThreadLocal可能导致内存泄漏的情况及其避免策略,特别是在线程池复用的背景下。
摘要由CSDN通过智能技术生成

阅读本篇博客您将了解如下内容:

  • TreadLocal的作用。
  • ThreadLocal的实现原理。
  • ThreadLocal是否会引起内存泄漏,在什么样的条件下引发,如何避免。

1、ThreadLocal的作用


使用线程封闭的指导思想来解决变量共享的并发安全问题,–可以简单理解为各玩各的雨女无瓜

2、ThreadLocal 源码初探

2.1 ThreadLocal的set方法的代码如下:

 // 获取当前线程对象
     Thread t = Thread.currentThread();
     // 根据线程对象获取ThreadLocalMap对象(ThreadLocalMap被Thread持有 threadLocals )
     ThreadLocalMap map = getMap(t);
     // 如果ThreadLocalMap存在,则直接插入;不存在,则新建ThreadLocalMap
     if (map != null){
         map.set(this, value);
         }else{
         createMap(t, value);
     }

threadLocals : 线程Thread 对象内部属性,这个属性默认就是null,由ThreadLocal.set 进行初始化。如果已经初始化了,则使用当前ThreadLocal的实例作为key,待存值作为value保存到当前线程的ThreadLocalMap变量中。
其实这里以前有个问题一直困扰我,为什么不直接给Thread的hreadLocals属性设置成object类型,然后set值的时候直接赋值给 threadLocals。
后面明白了一个Thread加一个ThreadLocal 只能保存一个val,但是一个Thread 可以对应多个ThreadLocal,一个线程对象属性可能被多个ThreadLocal共同持有。

 		ThreadLocal<String> th1 = new ThreadLocal<>();
        ThreadLocal<String> th2 = new ThreadLocal<>();
        th1.set("123");
        th2.set("456");
        System.out.println(th1.get());
        System.out.println(th2.get());

在这里插入图片描述

2.2 ThreadLocalMap是ThreadLocal 一个内部类,其本质和Map没有任何关系,也没有实现Map接口。内部使用Entry(类似Map key-value 对象) 数组存储数据,使用Hash 算法计算下标。

 static class ThreadLocalMap {

        /**
         *  使用弱引用包装ThreadLocal 作为map Key 
         *  在某些情况下key会被回收掉
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

     
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
           //使用hashCode 计算下标
          //这里使用hashCode 跟我们普通对象不一样,通过自增长计算出来
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

2.3 ThreadLocalMap的set方法,在出现hash冲突时,只是将下标向后移动,找到空闲的位置。正如set 方法注释上写,set 并不支持快速set、冲突了通过向后遍历数组找到空位置。

 private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
          // 循环里面处理hash冲突情况
            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) { 
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            // 当hash 冲突时,会一直向后找,直到有空位置
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //在位置i 后面搜寻是否有key 回收情况,则删除数组位置,返回true 
            // 当有删除,不需要判断扩容情况了,一个新增对应删除,容量都没有增加
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash(); //扩容数组
        }

2.4 ThreadLocalMap的get方法会通过向后遍历匹配出来,这个以HashMap 相比差距挺大的。插入、查找效率都在N之间。

  private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            //通过计算下标就找到
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        //向后查找符合要去key
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) { //遇到null 就停下来
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                     // k 已经被gc 了
                    //在数组中删除这个位置,这样可以帮助value 回收了
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

3、ThreadLocal会造成内存泄漏吗?

答案是会的。
1、当线程栈持有ThreadLocal,作为Entry key的它不会被gc,当ThreadLocalRef 引用失效时(线程生命周期结束),ThreadLocal 就会在下次gc时被回收掉。
但是目前咱们开发过程中都是使用池化的思想即使用线程池进而达到线程复用,所以线程并未结束,这种情况下ThreadLocalRef 就不会失效,但是它又作为ThreadLocalMap中Entry 的key,它不会被gc,导致内存泄漏,所以需要手动remove。

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值