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]以此类推。