一次性通过面试:ThreadLocal

ThreadLocal提供了线程独有的局部变量,可以在整个线程存活的过程中随时取用,极大地方便了一些逻辑的实现。常见的ThreadLocal用法有:

  • 存储单个线程上下文信息。比如存储id等;
  • 使变量线程安全。变量既然成为了每个线程内部的局部变量,自然就不会存在并发问题了;
  • 减少参数传递。比如做一个trace工具,能够输出工程从开始到结束的整个一次处理过程中所有的信息,从而方便debug。由于需要在工程各处随时取用,可放入ThreadLocal。
    下面罗列了几个面试经常会问到的问题,求职者熟记后定可通过面试。

一、ThreadLocal内部数据结构

在这里插入图片描述
在这里插入图片描述
ThreadLocal里面的实现,主要涉及到以下几个重要类:
1)Thread:大家很熟悉的线程类,一个Thread类自然代表一个线程。
2)ThreadLocal:既然本文是要解析ThreadLocal类,自然就离不开这个类啦~。
3)ThreadLocalMap:可以看成一个HashMap,但是它本身具体的实现并没有实现继承HashMap甚至跟java.util.Map都沾不上一点关系。只是内部的实现跟HashMap类似(通过哈希表的方式存储)。
4)ThreadLocalMap.Entry:把它看成是保存键值对的对象,其本质上是一个WeakReference弱引用对象。

熟记操作流程:
hreadLocal的set(T)函数中,首先是拿到当前线程Thread对象中的ThreadLocalMap对象实例threadLocals ,然后再将需要保存的值保存到threadLocals里面。 换句话说,每个线程引用的ThreadLocal副本值都是保存在当前线程Thread对象里面的。存储结构为ThreadLocalMap类型,ThreadLocalMap保存的键类型为ThreadLocal,值为副本值。
在这里插入图片描述
转载请注明转自:https://blog.csdn.net/huachao1001 https://blog.csdn.net/huachao1001/article/details/51970237,引入图说明。

二、为什么使用弱引用?为什么内存泄漏问题?

ThreadLocalMap.Entry:把它看成是保存键值对的对象,其本质上是一个WeakReference弱引用对象。

首先来说,如果把ThreadLocal置为null,那么意味着Heap中的ThreadLocal实例不在有强引用指向,只有弱引用存在,因此GC是可以回收这部分空间的,也就是key是可以回收的。但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。

因此,只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间内不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,比如使用线程池的时候,线程结束是不会销毁的,再次使用的,就可能出现内存泄露。

那么如何有效的避免呢?

事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!
还有就是不要放入指向型的对象作为value,不然可能外部还存在强引用,导致ThreadLocal中的value原来所指向的对象不能被GC。。。。。。

四、和线程池一起使用为什么会线程不安全?

使用线程池,归还线程之前要清除ThreadLocalMap,不然再取出该线程的时候,ThreadLocal还会存在。这不仅是内存泄露的问题,业务逻辑都可能会并发错误。可以重写ThreadPoolExecutor.afterExecute方法来处理。

protected void afterExecute(Runnable r, Throwable t) { 
    // you need to set this field via reflection.
    Thread.currentThread().threadLocals = null;
}

或者在使用时完成的业务类里面清理掉。

threadLocal.set(...);
try {
  ...
} finally {
  threadLocal.remove();
}

ThreadLocal最好还是不要和线程池一起使用。ThreadLocal放map也会导致map里面的数据越来越多,一定要清除掉。

五、Hash冲突?

和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。
默认16数组长度不够时,就扩容,然后再找到合适的位置。建议:每个线程存一个变量,或少量变量,减少Hash冲突的概率。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值