文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

​Thread Local深度解析,你学会了吗?

2024-11-30 01:56

关注

ThreadLocal概念

ThreadLocal也叫做本地线程变量,ThreadLocal中填充的是当前线程的变量,该变量对其他线程是隔离的,ThreadLocal在每个线程中都创建了一个变量副本,所以每个线程中的ThreadLocal都是一个独立的副本,自己可以访问自己线程内部的副本变量互不干扰。

ThreadLocal使用场景

ThreadLocal的使用也要看情况来定,按个人理解ThreadLocal大致会使用到以下场景:

说白了ThreadLocal就是做数据隔离,每条线程的ThreadLocal都是隔离的互不干扰,其实就是为了防止多线程环境下变量被其他线程篡改,只要记住这点在工作中什么场景下会使用到就一目了然了。

实际上Spring就是采用了Threadlocal来实现单个线程中的数据库操作使用的是同一个数据库连接,采用Threadlocal可以使业务层使用事务的时候不需要去管理connection对象,通过传播级别就能管理多个事务配置之间的切换,挂起和恢复。

Spring框架里面就是用的ThreadLocal来实现这种隔离,主要是在TransactionSynchronizationManager这个类里面,代码如下所示:

private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

private static final ThreadLocal> resources =
  new NamedThreadLocal<>("Transactional resources");

private static final ThreadLocal> synchronizations =
  new NamedThreadLocal<>("Transaction synchronizations");

private static final ThreadLocal currentTransactionName =
  new NamedThreadLocal<>("Current transaction name");

注意:在Spring5.2以后的版本Spring事务隔离从ThreadLocal换成了Mono响应式编程来实现隔离。

图片

ThreadLocal源码分析

图片

从源码上看其实ThreadLocal的set方法并不复杂
  1. 获取当前线程对象Thread.currentThread();
  2. 获取线程变量ThreadLocalMap map = getMap(t);
  3. 如果不为空则赋值map.set(this,value);
  4. 如果为空,初始化该线程对象的map变量,其中key为当前的threadlocal变量createMap(t,value);
再看看ThreadLocal的get方法

图片

图片

ThreadLocal如何做到线程隔离?

上面分析了ThreadLocal的set()和get()源码,在通过get()方法获取当前线程中副本变量为null那么直接创建一个ThreadLocalMap:

图片

从这里入手,看一下t.threadLocals。

图片

注释说得很清楚:ThreadLocal属于当前这个线程的。

注意:这个ThreadLocalMap是一个静态内部类。

图片

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. No operations are exported outside of the ThreadLocal class. The class is package private to allow declaration of fields in class Thread. To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.

到此为止其实ThreadLocal的数据隔离的真相就出来了,说白了每个线程Thread都维护了自己的一个threadLocals变量,当线程创建ThreadLocal的时候,实际上数据是存在自己的线程Thread的threadLocals变量里面,可以看出来这个ThreadLocalMap这个类只有一份,在线程中,所以实现了线程之间的隔离。

ThreadLocalMap底层原理

图片

虽然看着ThreadLocalMap很像是HashMap,实际上并没有实现Map接口,而是它的内部类Entry继承了WeakReference这个弱引用,也就是说不存在链表的关系了。

接下来我们来看一下ThreadLocalMap的set()方法(这里图片没有截全):

图片

ThreadLocalMap在存储的时候每次都会给每一个ThreadLocal对象一个threadLocalHashCode,在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,int i = key.threadLocalHashCode & (len - 1);

接下来判断如果当前位置为null,就初始化一个Entry对象放在位置上。

图片

如果当前位置i不为空,又刚好这个Entry对象的key正好是即将设置的key,那么就覆盖Entry中的value。

图片

如果位置i不为null并且key不等于 entry,那么就找下一个空位置,直到位置为空为止然后存放。

在get的时候就会根据ThreadLocal对象的Hash值,定位到相应位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下个位置。

如何共享ThreadLocal中的数据?

使用 InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。

问题是它们之间是如何实现传递的?

其实逻辑很简单,继续看Thread的源码,看下初始化的时候Thread.init做了什么操作:

图片

如果线程的inheritThreadLocals变量不为空的话,并且父线程的inheritThreadLocals不为空的话,就把线程的inheritThreadLocals给当前线程的inheritThreadLocals。

图片

关于ThreadLocal内存泄露

ThreadLocal使用不当也会出现问题:那就是内存泄露。

继续查看最开始存储数据的Entry类的源码:

图片

其实文档已经说得很直白了:

Note that null keys (i.e. entry.get()* == null 如果 key threadlocal 为 null 了,这个 entry 就可以清除了。

ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收 。

造成内存泄露的原因在于ThreadLocal为null,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap(thread 的内部属性)生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

再详细点来说,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。

按照道理一个线程使用完,ThreadLocalMap是应该要被清空的,但是现在线程被复用了。

解决办法:

每次在使用完ThreadLocal的时候一定要remove。

为什么ThreadLocal要使用弱引用?

如果使用强引用,当ThreadLocal 对象的引用(强引用)被回收了,ThreadLocalMap本身依然还持有ThreadLocal的强引用,如果没有手动删除这个key ,则ThreadLocal不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收, 可以认为这导致Entry内存泄漏。

来源:Westrice内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯