ThreadLocal

目录

ThreadLocal是什么?

ThreadLocal的作用?

ThreadLocal实现原理

ThreadLocalMap

ThreadLocalMap是如何处理hash冲突的?

ThreadLocalMap中的key为什么要设置为弱引用?


ThreadLocal是什么?

意如其名:本地线程变量

ThreadLocal变量是当前线程的变量,该变量对其他线程都是隔离的,ThreadLocal变量为每个线程都创建了一个内部变量。每个线程都可以访问自己的内部变量,其他线程不可访问。

ThreadLocal的作用?

  • ThreadLocal可以实现线程间的隔离,实现线程安全
  • ThreadLocal中每个线程可以实现资源共享(线程内)

ThreadLocal实现原理

ThreadLocal每个线程内都有一个内部的成员变量  ThreadLocalMap

起到线程隔离的是每个线程内的map集合,threadLocal起到的作用的关联资源对象

ThreadLocalMap这个变量用于存储资源对象

  • 调用set方法,就是以当前ThreadLocal作为Key,资源对象为Value,存入ThreadLocalMap中
  • 调用get方法,就是以当前ThreadLocal作为Key,从资源对象中获取相对应的资源
  • 调用remove方法,就是以当前ThreadLocal作为Key,从资源对象中移除相对应的资源

ThreadLocalMap

 由图可以看出 每个线程都有一个内部的map,并且都有独立的标识(1,2,3)

ThreadLocal就是通过这些对线程进行隔离的

ThreadlocalMap的初始容量capcity = 16,(负载因子)factor =    2/3

当元素超过 16 * 2/3 = 10.66666 = 10(向下取整)时扩容

ThreadLocalMap是如何处理hash冲突的?

在别的map中(hashmap,hashtable)等等中都是采用拉链法来处理hash冲突的

但是在ThreadLocalMap是采用开放寻址法解决hash冲突的

什么是开放寻址法?

比如在上图中thread-1中加入元素,当与key为a索引为0的元素进行了hash冲突,将会把元素放在下一个空闲的位置

 

 如图可见,与索引为0的元素出现hash冲突就将元素放在了下一个空闲的位置

ThreadLocalMap中的key为什么要设置为弱引用?

先查看源码(JDK11)

 static class ThreadLocalMap {
        private static final int INITIAL_CAPACITY = 16;
        private ThreadLocal.ThreadLocalMap.Entry[] table;
        private int size = 0;
        private int threshold;

        private void setThreshold(int len) {
            this.threshold = len * 2 / 3;
        }

        private static int nextIndex(int i, int len) {
            return i + 1 < len ? i + 1 : 0;
        }

        private static int prevIndex(int i, int len) {
            return i - 1 >= 0 ? i - 1 : len - 1;
        }

进入Entry数组中查看

static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

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

发现只有键super(k)调用了父类的构造(WeakRenference)把他变成了一个弱引用

为什么要这样设计呢?

仔细看下ThreadLocal内存结构就会发现,Entry数组对象通过ThreadLocalMap最终被Thread持有,并且是强引用。也就是说Entry数组对象的生命周期和当前线程一样。即使ThreadLocal对象被回收了,Entry数组对象也不一定被回收,这样就有可能发生内存泄漏。ThreadLocal在设计的时候就提供了一些补救措施:

  • Entry的key是弱引用的ThreadLocal对象,很容易被回收,导致key为null(但是value不为null)。所以在调用get()、set(T)、remove()等方法的时候,会自动清理key为null的Entity。
  • remove()方法就是用来清理无用对象,防止内存泄漏的。所以每次用完ThreadLocal后需要手动remove()。
  • 有些文章认为是弱引用导致了内存泄漏,其实是不对的。假设把弱引用变成强引用,这样无用的对象key和value都不为null,反而不利于GC,只能通过remove()方法手动清理,或者等待线程结束生命周期。也就是说ThreadLocalMap的生命周期由持有它的线程来决定,线程如果不进入terminated状态,ThreadLocalMap就不会被GC回收,这才是ThreadLocal内存泄露的原因。
  1. Thread可能要进行长时间的运行(如线程池中的线程),如果key不再使用,需要在内存不足GC时回收,释放其占用的内存,如果是强引用不再使用的key也回收不了
  2. GC仅仅是让key的内存释放,值内存的释放还需要酌情处理,释放时机:
    1. 获取(get) key发现null key(不推荐,容易造成内存泄漏)
    2. 设置(set) key时会使用启发式扫描,清除临近的null key,启发次数与元素个数,是否发现null key 有关(不推荐,容易造成内存泄漏)
    3. remove时(推荐),因为一般使用ThreadLocal时都把它作为静态变量,因此GC无法回收

参考文献:Java面试题视频教程,大厂Java面试突击技巧,工作几年和应届生必看的黑马程序员Java面试必考真题_哔哩哔哩_bilibili
ThreadLocal是什么?怎么用?为什么用它?有什么缺点? - 知乎 (zhihu.com)
 

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值