ThreadLocal

1.ThreadLocal概述

ThreadLocal是一个线程的本地变量,线程私有,不能与其他线程共享。避免资源竞争带来的多线程问题。

用于解决多线程并发访问共享变量(堆中的实例、静态属性和数组)的问题。

对于共享变量的访问受JMM模型的控制:(如下JMM模型)

在堆(也就是上图的主内存)中的变量在被线程使用的时候会被复制一个副本线程的本地内存中,当线程修改了共享变量之后就会通过JMM管理控制写会到主内存中。

​在多线程的场景下,当有多个线程对共享变量进行修改的时候,就会出现线程安全问题。两种解决多线程的线程安全问题,锁或者ThreadLocal。

ThreadLocal和加锁的区别:

2.Thread Local的使用方法

ThreadLocal<String> tl=new ThreadLocal<String>();

tl.set("muse");

tl.get();

3.源码解析

3.1 ThreadLocal Thread ThreadLocalMap Entry四者之间的关系

ThreadLocalMap是Thread的一个变量。每个Thread维护着一个ThreadLocalMap的引用getMap(Thread.currentThread());

ThreadLocalMap是ThreadLocal的一个静态内部类,里边定义了一个Entry保存数据,key是ThreadLocal对象。(弱引用)

ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

3.2 以下略参考讲义

4.内存溢出问题

当ThreadLocal Ref被回收,Entry使用的是弱引用,在下次垃圾回收中,就会把ThreadLocal对象清除,这个时候Rntry中KEY=null,但是ThreadLocal Map中仍然有Current Thread Ref这个强引用,因此Entry中的value值无法清除。还是存在内存泄漏的问题。

由此可以发现,使用ThreadLocal造成内存泄露的问题是因为:ThreadLocalMap的生命周期与Thread一致,如果不手动清除掉Entry对象的话就可能会造成内存泄露问题。因此,需要我们在每次在使用完之后需要手动的remove掉Entry对象。

在线程池的线程复用场景中在线程执行完毕时一定要调用remove,避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存(这个时候ThreadLocalMap和里面的元素是不会回收掉的)。

总结:存在内存泄露的有两个地方:ThreadLocal和Entry中Value。最保险还是要注意要自己及时调用remove方法!!!

5.应用场景(待完善)

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

2、线程间数据隔离

每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Random)。

依赖于ThreadLocal本身的特性,对于需要进行线程隔离的变量可以使用ThreadLocal进行封装。

3、进行事务操作,用于存储线程事务信息。

假如在我们的业务方法中需要调用其他方法,同时其他方法都需要用到同一个对象时,可以使用ThreadLocal替代参数的传递或者static静态全局变量。这是因为使用参数传递造成代码的耦合度高,使用静态全局变量在多线程环境下不安全。当该对象用ThreadLocal包装过后,就可以保证在该线程中独此一份,同时和其他线程隔离。

​ 例如在Spring的@Transaction事务声明的注解中就使用ThreadLocal保存了当前的Connection对象,避免在本次调用的不同方法中使用不同的Connection对象。

4、数据库连接,Session会话管理。

全局存储用户信息

可以尝试使用ThreadLocal替代Session的使用,当用户要访问需要授权的接口的时候,可以现在拦截器中将用户的Token存入ThreadLocal中;之后在本次访问中任何需要用户用户信息的都可以直接冲ThreadLocal中拿取数据。

知识点补充:

Q:为什么ThreadLocal一般用static修饰?

A:为了避免重复创建TSO(thread specific object,即与线程相关的变量。),一个ThreadLocal实例对应当前线程中的一个TSO实例。因此,如果把ThreadLocal声明为某个类的实例变量(而不是静态变量),那么每创建一个该类的实例就会导致一个新的TSO实例被创建。显然,这些被创建的TSO实例是同一个类的实例。于是,同一个线程可能会访问到同一个TSO(指类)的不同实例,这即便不会导致错误,也会导致浪费(重复创建等同的对象)

Q:为什么使用弱引用?

A:在ThreadLocalMap的set/getEntry中,会对key进行判断,如果key为null,那么value也会被设置为null,这样即使在忘记调用了remove方法,当ThreadLocal被销毁时,对应value的内容也会被清空。多一层保障!

Q:JDK8之后ThreadLocal的设计有什么不同?有什么好处?

A: 在JDK早期的设计中,每个ThreadLocal都有一个map对象,将线程作为map对象的key,要存储的变量作为map的value。

JDK8之后,每个Thread维护一个ThreadLocalMap对象,这个Map的key是ThreadLocal实例本身,value是存储的值要隔离的变量,是泛型,其具体过程如下:

每个Thread线程内部都有一个Map(ThreadLocalMap::threadlocals);

Map里面存储ThreadLocal对象(key)和线程的变量副本(value);

Thread内部的Map由ThreadLocal维护,由ThreadLocal负责向map获取和设置变量值;

对于不同的线程,每次获取副本值时,别的线程不能获取当前线程的副本值,就形成了数据之间的隔离。

JDK8之后设计的好处在于:

每个Map存储的Entry的数量变少,在实际开发过程中,ThreadLocal的数量往往要少于Thread的数量,Entry的数量减少就可以减少哈希冲突。

当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存使用,早期的ThreadLocal并不会自动销毁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值