前言
ThreadLocal:线程 + 本地 -> 线程本地变量(所以说我觉得它取名叫ThreadLocalVariable获取还更能让人易懂些),它的出镜率可不低。虽然写业务代码一般用不着,但它是开源工具的常客:用于在线程生命周期内传递数据。
有的人说,每看一遍ThreadLocal都会有新的感受,这其实是比较诡异的现象,因为我认为“真理”是不应该经常变的(或者说是不可能变化的)。我自己百度了一波,关于ThreadLocal的文章满天飞,有讲使用的亦有讲原理的,鱼龙混杂。其中有一派文章主旨讲述:使用ThreadLocal解决多线程程序的并发问题,使用该工具写出简洁、优美的多线程程序…
这类水文不在少数,大有占据主流的意思,它对初学者的误导性非常大,从而造成了每看一遍都会有新感受的错觉。本文为社区贡献一份微薄之力,在这里教你完全正确的使用ThreadLocal的姿势,避免你以后再犯迷糊。
正文
本文的内容并不讲述ThreadLocal/InheritableThreadLocal的源码、原理,一方面确实不难,另一方面关于它的源码、原理讲解的相关文章确实不在少数。
ThreadLocal是什么?
我们从字面上的意思来理解ThreadLocal,Thread:线程;Local:本地的,局部的。也就是说,ThreadLocal是线程本地的变量,只要是本线程内都可以使用,线程结束了,那么相应的线程本地变量也就跟随着线程消失了。
ThreadLocal和InheritableThreadLocal均是JDK1.2新增的API,在JDK5后支持到了泛型。它表示线程局部变量:为当前线程绑定一个变量,这样在线程的声明周期内的任何地方均可取出。
说明:InheritableThreadLocal继承自ThreadLocal,在其基础上扩展了能力:不仅可在本线程内获取绑定的变量,在子线程内亦可获取到。(请注意:必须是子线程,若你是线程池就可能不行喽,因为线程池里的线程是实现初始化好的,并不一定是你的子线程~)
它仅有如下三个public方法:
public void set(T value) { ... } public T get() { ... } public void remove() { ... }
分别代表:
- 设置值:把value和当前线程绑定
- 获取值:获取和当前线程绑定的变量值
- 删除值:移除绑定关系
说明:虽然每个绑定关系都是使用的WeakReference,但是还是建议你显示的做好remove()移除动作,否则容易造成内存泄漏。当然关于ThreadLocal内存泄漏并不是本文的内容,有兴趣可以自行去了解。
另外对于解释ThreadLocal是什么,建议可参考下它的Javadoc:
* This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID).
大致意思是:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量 (通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。 ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程 (例如,用户 ID 或事务 ID)相关联。
更准确的说,一般我们使用ThreadLocal
是作为private static final
字段来使用的~
ThreadLocal怎么用?
知道了ThreadLocal
是什么后,怎么用其实就非常简单了。看如下这个简单示例:
本例模拟使用Person对象和当前线程绑定:
@Setter @ToString private static class Person { private Integer age = 18; }
书写测试代码:
private static final ThreadLocal<Person> THREAD_LOCAL = new ThreadLocal<>(); @Test public void fun1() { // 方法入口处,设置一个变量和当前线程绑定 setData(new Person()); // 调用其它方法,其它方法内部也能获取到刚放进去的变量 getAndPrintData(); System.out.println("======== Finish ========="); } private void setData(Person person){ System.out.println("set数据,线程名:" + Thread.currentThread().getName()); THREAD_LOCAL.set(person); } private void getAndPrintData() { // 拿到当前线程绑定的一个变量,然后做逻辑(本处只打印) Person person = THREAD_LOCAL.get(); System.out.println("get数据,线程名:" + Thread.currentThread().getName() + ",数据为:" + person); }
运行程序打印输出:
set数据,线程名:main get数据,线程名:main,数据为:Test2.Person(age=18) ======== Finish =========
这便是ThreadLocal的典型应用场景:方法调用间传参,并不一定必须得从方法入参处传入进来,还可以通过ThreadLocal来传递,进而在该线程生命周期内任何地方均可获取到,非常的方便有木有。
小细节:set和get数据时的线程是同一个线程:均未main线程
局限性
上例是ThreadLocal的典型应用场景,大部分情况下均能正常work。但是,在当下互联网环境下,经常会用到了异步方式来提高程序运行效率,比如如上方法调用getAndPrintData()因比较耗时所以我希望异步去进行,改造如下:
@Test public void fun1() throws InterruptedException { // 方法入口处,设置一个变量和当前线程绑定 setData(new Person()); // getAndPrintData(); // 异步获取数据 Thread subThread = new Thread(() -> getAndPrintData()); subThread.start(); subThread.join(); // 非异步方式获:在主线程里获取 getAndPrintData(); System.out.println("======== Finish ========="); }
运行程序,打印输出:
set数据,线程名:main get数据,线程名:Thread-0,数据为:null get数据,线程名:main,数据为:Test2.Person(age=18) ======== Finish =========
线程名为Thread-0的子线程里并没有获取到数据,只因为它并不是当前线程,貌似合情合理,这便是ThreadLocal的局限性。
那既然这是一个常见需求,除了把变量作为方法入参传进去,有没有什么更为便捷的方案呢?有的,JDK扩展了ThreadLocal提供了一个子类:InheritableThreadLocal,它能够向子线程传递数据。