ThreadLocal的使用

ThreadLocal是jdk1.2时引入的一个线程局部变量。我们知道,多线程产生的原因就是因为多个线程间存在共享变量。如果我们每个线程都有属于自己的变量。就不会存在变量共享的问题了。ThreadLocal对象的作用就是用来实现每个线程一个变量的作用,每个线程有自己的变量,自己用自己的,就不会出现共享的问题了。这是从字面意思上解释。下面我们从源码层面分析,ThreadLocal是怎么和当前线程绑定起来的 ?我们进Thread的源码看一下。在源码中可以看到一个threadLocals的成员变量。类型为ThreadLocalMap。在这里插入图片描述
这个ThreadLocalMap是干嘛的呢?
点击ThreadLocalMap,去看它的源码:
在这里插入图片描述
ThreadLocalMap是ThreadLocal的一个内部类。ThreadLocalMap是一个映射表,可以理解成HashMap结构。key是当前Thread对象,value为一个Entry对象。Entry对象,我们下面会介绍,先说这个ThreadLocalMap对象,很明显它在Thread中的值为null,那么它是在哪个地方进行初始化的呢?
一共有两个地方,一个是set方法中,一个是get方法中
先看get方法的源码:
在这里插入图片描述
如果我们没有往ThreadLocal对象中set值,直接就调用get方法的话,map为空。此时就会调用setInitialValue方法,初始化一个ThreadLocalMap对象。如果map不为空,会根据当前线程对象从ThreadLocalMap中取出当前线程对象对应的value值。我们可以看到,map先是获取了一个Entry对象,真正的值是放在这个Entry对象中,下面我们会详细说出。
在这里插入图片描述
注意这里还有一个initialValue方法,这个方法是给ThreadLocal赋初始化值的。初始是这样的
在这里插入图片描述
我们可以看到该方法的访问权限是protected,也就是子类复写使用。我们可以overriden该方法。初次调用get方法时,赋一个初始值。
好了,继续回到ThreadLocalMap的初始化问题。除了上面说的get方法。还有就是在set方法中,继续看源码:
在这里插入图片描述
说完ThreadLocalMap对象的初始化,继续说Entry对象。这个Entry对象又是什么呢?
点进源码去看看:
在这里插入图片描述
可以看到,Entry对象继承了弱引用对象WeakReference,弱引用对象意为:在GC时会回收的对象。( java一共有4种引用类型,弱引用是其中一种,相关知识可以百度了解一下)。从构造函数中可以看到,Entry将key,也就是当前ThreadLocal对象传递给了弱引用,所以在GC时,如果ThreadLocal对象没有强引用的话,当前Entry中的key(ThreadLocal对象)会被GC回收。Thread,ThreadLocal,ThreadLocalMap,Entry。这几个对象之间的关系是怎么样的呢?
在这里插入图片描述
上图可以看出,ThreadLocalMap中维护的是一个Entry数组?
为什么ThreadLocalMap中引用的Entry是一个数组类型呢?因为在一个线程中,我们可以new多个ThreadLocal对象。每一个ThreadLocal对象就对应着一个Entry对象,所以要使用数组存储这些Entry对象。
这里有一个问题,为什么ThreadLocal的作者要将Entry中的key设计成弱引用呢?
作者的想法是:如果当前ThreadLocal对象没有强引用存在,就通知GC回收该key,此时key变为null。同时,作者在set,get中,都对key为null的情况做了处理。会清除掉key为null的Entry对象。这样就可以避免,我们使用了set和get方法,但是没有显示调用remove清除该key的问题。一定程度上避免了内存泄露的问题。
虽然作者做了很多处理,但是由于线程池技术的普及,ThreadLocal对象使用不当,还是会造成内存泄露。为什么呢?
原因是:我们在线程池中取出的链接不会销毁,会返回到池中复用。如果我们对ThreadLocal对象手动set了一个值,但是后期没有再次调用set和get方法。下一次GC发生时,会回收掉key,但是由于value为一个强引用,所以导致key为null,永远无法被取出。该块内存空间永远无法被使用。内存泄露。
所以,我们在使用ThreadLocal对象时,用完的值要注意执行remove操作。
另外,ThreadLocal的最佳实践是用static关键字修饰,防止产生多个ThreadLocal对象,内存浪费。

说完了ThreadLocal结构,说说ThreadLocal的存值和取值以及移除值
存值的方法为set,取值的方法为get。
在这里插入图片描述
set方法挺清晰的,就是获取当前线程对象,然后获取ThreadLocalMap映射表对象。
看一下getMap方法的源码
在这里插入图片描述
简单粗暴,直接返回Thread类中的threadLocals变量。
如果map不为空,说明我们之前set过值。这个时候,就会进入set(this,value)的set重载方法。这个this,很明显就是当前的ThreadLocal对象。value就是我们想要存储在ThreadLocal中的值。
我们进入set(this,value)方法中看一下
在这里插入图片描述
首先获取Entry数组,然后利用算法计算Entry数组的一个索引下标,准备存放新的Entry对象。如果当前ThreadLocal对象关联的Entry对象在Entry数组中已经存在,就覆盖原来的值。如果计算的索引所关联的Entry对象的key为空。说明存在GC回收掉key的情况,这个时候就会清除掉该key对应的value以及Entry对象。
如果在for循环中没有返回的话,就会新建一个新的Entry。然后将Entry数组中的长度加1,同时判断该数组是否需要扩展。

get方法的源码为:
在这里插入图片描述
get方法就比较简单了,就是利用当前线程找到对应的Entry对象,返回它的value值即可。如果没有对应的ThreadLocalMap对象,就会创建一个新的Entry对象。get时,也会判断是否有空key的Entry,然后进行清除。

下面再看一下remove方法
在这里插入图片描述
首先根据key计算一下Entry所在的索引,然后判断是否为空,如果不为空,就判断该下标对应的key是否等于我们传入的key,如果相等,就会清除掉Entry对象对应的key,同时清除掉value。同时还有这个Entry对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是一个Java类,用于在多线程环境下保存线程本地变量的副本。通过创建ThreadLocal对象,每个线程都可以拥有自己独立的变量副本,互不干扰。 使用ThreadLocal的过程如下: 1. 创建一个ThreadLocal对象,可以指定泛型类型。 2. 在每个线程中,通过ThreadLocal对象的`get()`方法获取当前线程的变量副本。如果当前线程没有设置过变量值,则会使用默认值进行初始化。 3. 在每个线程中,通过ThreadLocal对象的`set(value)`方法设置当前线程的变量值。 4. 在每个线程中,通过ThreadLocal对象的`remove()`方法移除当前线程的变量副本。 需要注意的是,ThreadLocal对象的生命周期与Thread对象的生命周期一样长。当ThreadLocal对象被垃圾回收时,关联的变量副本也会被回收。 在ThreadLocal内部,使用ThreadLocalMap来存储每个线程的变量副本。ThreadLocal的实例作为key,变量值作为value。ThreadLocalMap可以使用强引用或弱引用来引用ThreadLocal对象。如果使用强引用,当ThreadLocal对象被回收时,如果没有手动删除对应的变量副本,会导致内存泄漏。如果使用弱引用,当ThreadLocal对象被回收时,对应的变量副本也会被回收。 总结来说,ThreadLocal是一种线程本地变量,通过保存每个线程的变量副本,实现了多线程环境下的线程隔离。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ThreadLocal使用与原理](https://blog.csdn.net/qq_35190492/article/details/116431270)[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_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [ThreadLocal使用详解](https://blog.csdn.net/LJJZJ/article/details/88763666)[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_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值