ThreadLocal

1、什么是ThreadLocal

**ThreadLocal类用来提供线程内部的局部变量。**这种变量在多线程环境下(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联各个线程和线程上下文。
特性:

(1)线程安全:在多线程并发场景下保证线程安全

(2)传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量。

(3)线程隔离:每个线程的变量都是独立的,不会互相影响。

2、基本使用

方法声明 描述
ThreadLocal() 创建ThreadLocal对象
public void set(T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量

3、ThreadLocal的优点

(1)传递数据:保存各个线程绑定的数据,在需要的地方可以直接获取,避免线程直接传递带来的代码耦合问题

(2)线程隔离:各线程之间的数据相互隔离又具有并发性,避免同步方式带来的性能损失
ThreadLocal在Spring事务中的应用

spring的事务就借助了ThreadLocal类。Spring会从数据库连接池中获得一个connection,然后会把connection放进ThreadLocal中,也就和线程绑定了,事务需要提交或回滚,只要从ThreadLocal中拿到connection进行操作。

4、ThreadLocal的底层结构

JDK早期:ThreadLocal是这样设计的,每个ThreadLocal都创建一个Map,然后用线程作为Map的key,要存储的局部变量作为Map的value,这样就能达到各个线程的局部变量隔离的效果。但现在不是了。

JDK8中ThreadLocal的设计为:每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。

这样设计的好处:

(1)这样设计的好处就是每个Map存储的Entry数量就会变少。因为之前的存储数量是由Thread数量决定,现在是由ThreadLocal数量决定。在实际应用中,ThreadLocal数量要少于Thread的数量。

(2)当Thread销毁时,对应的ThreadLocalMap也会随之销毁,能减少内存的使用。

在ThreadLocalMap中也是用Entry来保存k-v结构数据的。不过Entry中的key只能是ThreadLocal对象,这点在构造方法中已经限定死了。

另外Entry继承WeakReferece,也就是key(ThreadLocal)是弱引用,其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。

5、弱引用和内存泄漏

在使用ThreadLocal的过程中会发现有内存泄露的情况发生,就猜测这个内存泄露跟Entry中使用了弱引用的key有关系, 这个理解是不对的。

因为如果key使用强引用,内存泄露也是无法避免的。假设在业务代码中使用完ThreadLocal,thread ref被回收了,但是因为threadLocalMap中的Entry强引用了ThreadLocal,造成ThreadLocal无法被回收。

如果key使用了弱引用,假设业务代码中使用完了ThreadLocal,threadLocal ref被回收了,由于ThreadLocalMap只持有ThreadLocal的弱引用,没有任何强引用指向threadLocal实例,所以treadLocal实例被gc顺利回收,此时Entry中的key=null。但是在没有手动删除这个Entry及CurrentThread依然运行的前提下,也存在有强引用链threadRef->currentThread->threadLocalMap->entry->value,value不会被回收,而这块value永远不会被访问到了,导致value内存泄露。也就是说ThreadLocalMap中的key使用了弱引用,也有可能内存泄露。
出现内存泄露的真实原因

ThreadLocal内存泄露的根源是由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应的key就会导致内存泄漏
为什么使用弱引用

既然无论ThreadLocalMap中的key使用哪种类型引用都无法避免内存泄露,跟使用弱引用没有关系。

事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(即ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null。这意味着使用完ThreadLocal,CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用get/set/remove中的任一方法的时候会被清除,从而避免内存泄露。

6、hash冲突的解决

ThreadLocalMap是使用线性探测法来解决哈希冲突的。可以把Entry[]看成一个环形数组,如果table[index]上已经有值并且key与当前key不一致,那么就发生了哈希冲突。如果table[15]发生了哈希冲突,这时候冲突会回到0,取table[0]以此类推。

相关推荐
rhyme2 个月前
源码浅析:SpringBoot main方法结束为什么程序不停止
springboot·markdown·java多线程·源码解析·mermaid
佛祖让我来巡山5 个月前
【多线程】Java多线程与并发编程全解析
java多线程·java并发编程
橙-极纪元6 个月前
【总结篇】java多线程,新建线程有几种写法,以及每种写法的优劣势
java·开发语言·java多线程·新建线程有几种写法
娅娅梨8 个月前
synchronized实现原理
java·jvm·java多线程·synchronized
linweidong10 个月前
唯品会Android面试题及参考答案
android·java多线程·内存泄漏·anr·aidl·安卓面试·安卓面经
_whitepure1 年前
CAS详解
cas·java多线程·unsafe·aba问题·cas原理
_whitepure1 年前
ThreadLocal详解
java多线程·threadlocal·threadlocal原理
王文搏2 年前
java中的单例模式
java·开发语言·单例模式·java多线程
解码猿2 年前
深入浅出Java多线程(十三):阻塞队列
java·jvm·java多线程