ThreadLocal最全详解(万字图文总结)

ThreadLocal最全详解(万字图文总结)-mikechen

ThreadLocal是实现Java并发编程非常重要的一个组件,也是大厂喜欢考察的内容,下面我就全面来详解ThreadLocal@mikechen

ThreadLocal

ThreadLocal提供了线程的本地变量,是 Java 中用于实现线程局部变量的类,它提供了线程内部的独立变量。

即即每个线程都有一个独立的”变量副本”,不会与其他线程的”变量副本”产生冲突。

如下图所示:

ThreadLocal最全详解(万字图文总结)-mikechen

每个线程都有自己的独立资源,可以通过 ThreadLocal 对象访问它自己的独立变量。

ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。

ThreadLocal主要用于:解决多线程并发时访问共享变量的问题,主要是做数据隔离。

 

ThreadLocal原理

ThreadLocal原理:ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象。

这个 Map 不是直接使用 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类,用来存变量。

它的大概结构如下图所示:

ThreadLocal最全详解(万字图文总结)-mikechen

ThreadLocalMap

ThreadLocalMapThreadLocal 的核心存储结构,类似于 HashMap,但设计上有所不同:

ThreadLocalMapThreadLocal 的内部静态类,是一个自定义的哈希表,专门用于存储与 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最全详解(万字图文总结)-mikechen

ThreadLocalMap中包含了多个ThreadLocal对象,那么如果一个线程使用多个ThreadLocal对象,ThreadLocalMap如何区分不同的ThreadLocal呢?

实际上,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在KV数组中找到对应的本地变量。

ThreadLocal最全详解(万字图文总结)-mikechen

key是ThreadLocal对象的弱引用,之所以使用 WeakReference 类型作为ThreadLocal对象的引用,是出于垃圾回收考虑。

当某个 ThreadLocal对象已经没有强引用指向时,它被 GC 回收,那么它的 ThreadLocalMap 里对应的 Entry 的键值会随之失效。

不过需要注意的是,虽然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种典型的使用场景:

ThreadLocal最全详解(万字图文总结)-mikechen

1.解决线程安全问题

ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全。

 

2.代替参数的显式传递

当我们在写API接口的时候,通常Controller层会接受来自前端的入参。

当这个接口功能比较复杂的时候,通常情况下,我们会在每个调用的方法上加上需要传递的参数。

但是,如果我们将参数存入ThreadLocal中,那么就不用显式的传递参数了,而是只需要ThreadLocal中获取即可。

这是因为:使用参数传递造成代码的耦合度高,使用静态全局变量在多线程环境下不安全,当该对象用ThreadLocal包装过后,就可以保证在该线程中独此一份,同时和其他线程隔离。

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

 

3.全局存储用户信息

可以尝试使用ThreadLocal替代Session的使用,每个线程拥有独立的 Session 对象。

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

作者简介

陈睿|mikechen,10年+大厂架构经验,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

关注作者「mikechen」公众号,获取更多技术干货!

后台回复架构,即可获取《阿里架构师进阶专题全部合集》,后台回复面试即可获取《史上最全阿里Java面试题总结

评论交流
    说说你的看法