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对象。
如下图所示:
实际上,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在KV数组中找到对应的本地变量。
不过需要注意的是,虽然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中拿取数据。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》