大家好,我是mikechen。
ThreadLocal是实现Java并发编程非常重要的一个组件,也是大厂喜欢考察的内容,下面我就全面来详解ThreadLocal@mikechen
ThreadLocal
ThreadLocal提供了线程的本地变量,是 Java 中用于实现线程局部变量的类,它提供了线程内部的独立变量。
即即每个线程都有一个独立的"变量副本",不会与其他线程的"变量副本"产生冲突。
如下图所示:
图片
每个线程都有自己的独立资源,可以通过 ThreadLocal 对象访问它自己的独立变量。
ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。
ThreadLocal主要用于:解决多线程并发时访问共享变量的问题,主要是做数据隔离。
ThreadLocal原理
ThreadLocal原理:ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象。
这个 Map 不是直接使用 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类,用来存变量。
它的大概结构如下图所示:
图片
ThreadLocalMap
ThreadLocalMap 是 ThreadLocal 的核心存储结构,类似于 HashMap,但设计上有所不同:
ThreadLocalMap 是 ThreadLocal 的内部静态类,是一个自定义的哈希表,专门用于存储与 ThreadLocal 关联的数据。
每个线程都持有一个 ThreadLocalMap 实例,以存储它的 ThreadLocal 变量和对应的值。
ThreadLocalMap是一个Map,key是ThreadLocal,value是Object。
Entry 类
除此之外,ThreadLocalMap内部持有一个 Entry[] 类型的数组 table,每个数组成员都是一个键值对,Entry数组是真正承载数据的地方。
ThreadLocalMap.Entry 继承自 Java 标准库中的 WeakReference<ThreadLocal<?>>,它的核心结构如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
//key就是一个弱引用
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry继承自WeakReference,每个Entry 的 key 都是一个 ThreadLocal 对象的弱引用,Java 中的弱引用允许对一个对象的引用在没有强引用的情况下,被垃圾回收器回收。
value 是Object 类型,是强引用,ThreadLocalMap 中可以包含多个ThreadLocal对象。
如下图所示:
图片
ThreadLocalMap中包含了多个ThreadLocal对象,那么如果一个线程使用多个ThreadLocal对象,ThreadLocalMap如何区分不同的ThreadLocal呢?
实际上,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在KV数组中找到对应的本地变量。
图片
key是ThreadLocal对象的弱引用,之所以使用 WeakReference 类型作为ThreadLocal对象的引用,是出于垃圾回收考虑。
不过需要注意的是,虽然key值是弱引用,不影响ThreadLocal对象回收,但value值是强引用。
当ThreadLocal被回收,value对象不会被回收,可能会引发内存泄漏。
所以,记得要调用 remove() 方法,避免内存泄露。
ThreadLocal使用
ThreadLocal的用法,这个类提供thread-local变量,这些变量与线程的局部变量不同,每个线程都保存一份改变量的副本,可以通过get、或者set方法访问。
如下所示:
//创建
private ThreadLocal threadLocal = new ThreadLocal();
//一旦创建了ThreadLocal,就可以使用它的set()方法设置要存储在其中的值
threadLocal.set("A thread local value");
//获取值
String threadLocalValue = (String) threadLocal.get();
//移除一个值
threadLocal.remove();
ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的,通过get和set方法就可以得到当前线程对应的值。
由于ThreadLocal里设置的值,只有当前线程自己看得见,这意味着你不可能通过其他线程为它初始化值,为了弥补这一点,ThreadLocal提供了一个withInitial()方法统一初始化所有线程的ThreadLocal的值。
如下所示:
private ThreadLocal<Integer> localInt = ThreadLocal.withInitial(() -> 18);
上述代码将ThreadLocal的初始值设置为18,这对全体线程都是可见的。
ThreadLocal应用
在通常的业务开发中,ThreadLocal 有以下3种典型的使用场景:
图片
1.解决线程安全问题
ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全。
2.代替参数的显式传递
当我们在写API接口的时候,通常Controller层会接受来自前端的入参。
当这个接口功能比较复杂的时候,通常情况下,我们会在每个调用的方法上加上需要传递的参数。
但是,如果我们将参数存入ThreadLocal中,那么就不用显式的传递参数了,而是只需要ThreadLocal中获取即可。
这是因为:使用参数传递造成代码的耦合度高,使用静态全局变量在多线程环境下不安全,当该对象用ThreadLocal包装过后,就可以保证在该线程中独此一份,同时和其他线程隔离。
比如:在Spring的@Transaction事务声明的注解中,就使用ThreadLocal保存了当前的Connection对象,避免在本次调用的不同方法中使用不同的Connection对象。
3.全局存储用户信息
可以尝试使用ThreadLocal替代Session的使用,每个线程拥有独立的 Session 对象。
当用户要访问需要授权的接口的时候,可以现在拦截器中将用户的Token存入ThreadLocal中,之后在本次访问中任何需要用户用户信息的都可以直接冲ThreadLocal中拿取数据。