Java 的世界中有一道由 ThreadLocal 组成的墙,外面的人想进去,里面的人想出来。
今天就带你打开 ThreadLocal 的大门,并且希望你可能会得到一些不一样的东西。
文章目录
什么是 ThreadLocal ?
看一看 JDK 官方怎么说?
这个类提供了线程局部变量(thread-local variables)。这些变量与普通变量的不同之处在于,每个访问该变量的线程(通过其get或set方法)都有自己的、独立初始化的变量副本。
ThreadLocal实例通常是类中的私有静态字段,希望将状态与线程联系起来(例如,用户ID或事务ID)。
例如,下面的类产生了每个线程的本地唯一标识符。一个线程的ID在它第一次调用ThreadId.get()时被分配,并在后续调用中保持不变。
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
只要线程还活着,并且ThreadLocal实例可被访问,每个线程都持有对其线程-本地变量副本的隐式引用;在一个线程离开后,其所有线程-本地实例的副本都会被垃圾回收(除非存在对这些副本的其他引用)。
可以看出几点:
- ThreadLocal 的使用形态:通常以私有静态字段形式出现。
- 使用 ThreadLocal 的场景是:使状态(即数据)和线程关联(可以理解数据线程隔离,至于怎么实现隔离的,后面会分析)。
- 如何理解每个线程都拥有自己独立变化的副本这句话?我想很多人都理解错了,后面分析。
ThreadLocal 的常用方法简介
-
public void set(T value)
故名思意,这个方法是用来存值的。
-
public T get()
依然故名思意,这个方法是用来取值的。取哪里的值呢?可以取上面通过 set 方法设置的值。
-
public void remove()
依然故名思意,这个方法是用例清空(清空哪里的值呢?后续分析)值的,清空之后 get() 方法默认返回 null;
ThreadLocal 的常用方法源码分析
简单从源码的角度分析一下,上述常用方法的实现原理。
提示:请带着问题看源码分析或者看完回头回答如下问题,以检验你对 ThreadLocal 的理解程度:
- ThreadLocal 和 Thread 的关系是什么?
- 数据存储在哪里?
- ThreadLocal 是个 map 结构嘛?
- 数据存储在 ThreadLocal 中嘛?
- ThreadLocal 实例本身会变嘛?
- 一个线程可以绑定多个 ThreadLocal 嘛?
- 多个线程共享同一个 ThreadLocal 实例嘛?
ThreadLocal 源码概览
简单看一下源码的样子,只是看一下方法的样子,一回生二回熟,后面详细解读,给你带来不一样的体验,结论说不定会冲击到你哦。
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public T get() {
Thread t = Thread.currentThread();//1.
ThreadLocalMap map = getMap(t);//2.
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//3.
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
}
public void set(T value) 分析
使用 ThreadLocal 在当前线程上设置值
public void set(T value) {
Thread t = Thread.currentThread();// 1.
ThreadLocalMap map = getMap(t);// 2.
if (map != null)
map.set(this, value);// 3.
else
createMap(t, value);// 4.
}
// 2.1
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
-
获取当前线程(虽然简单,但重要,后面可以发现线程其实才是数据的载体)
-
获取当前线程所绑定的一个类Map对象(关键字:该线程绑定的对象),以下简称map。
-
2.1 可以看到,直接返回的是 Thread 对象上的一个 ThreadLocalMap 类型的变量
-
那么,是不是可以体会到线程隔离的意思了?数据存储在指定线程内,当然是线程隔离的了。
-
-
将 value 设置进 map,key 为 ThreadLocal (划重点啦)实例对象,value 为你要存储的值。
-
初始化一个 map,并执行步骤 3 设置值。
public T get() 分析
使用 ThreadLocal 获取绑定在当前线程的值
public T get() {
Thread t = Thread.currentThread();//1.
ThreadLocalMap map = getMap(t);//2.
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//3.
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();// 4.
}
- 获取当前变量
- 获取当前线程所绑定的一个类Map对象,以下简称map,同 set 方法。
- 以 ThreadLocal 作为 key 从 map 中取出其绑定的值,有没有感觉,ThreadLocal 就是线程数据绑定的标识而已?
- 如果没有设置过值(或者被 remove 过了),那么执行初始化动作。
public void remove() 分析
清空当前线程 在 ThreadLocal 上绑定的值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());//1.
if (m != null)
m.remove(this);
}
-
获取当前线程所绑定的一个类Map对象
-
以 ThreadLocal 对象作为 key,删除其绑定的值
ThreadLocal 小结
- ThreadLocal 其实并不存储数据,真正的数据存储在线程中
- 之所以可以做到数据线程之间隔离,只是因为数据存储在指定线程内部,并不是 ThreadLocal 有什么魔法能力
- 所以,ThreadLocal 也并没有什么变量拷贝一份,只是作为一个 key 的作用,用来关联其绑定的值
- 所以,ThreadLocal 并不是解决的线程之间数据共享的问题,而是更加保守的感觉,线程私有
- 使用 ThreadLocal 设置值时,key 是 ThreadLocal 对象本身,key 在多个线程间是共享的
- 所以,ThreadLocal 本省既不存储数据,也没有变化
- 所以,一个线程可以对应多个 ThreadLocal 对象
- 可以将 ThreadLocal 简单理解为绑定数据的“记号”
灵魂发问:ThreadLocal 的真正意图是什么???
这个问题抛出的原因是,你在网络上搜索 ThreadLocal 进行学习时,总有会看到头部大佬的文章。
而有些人的文章里,好像会向你传递一个信息:使用 ThreadLocal 可以节约资源,只使用少量变量对象即可。
这对吗?
大佬们常用的例子大多和 事务连接 或 SimpleDateFormat 相关
常用话术:SimpleDateFormat
,每次使用都new一个多浪费性能呀…所以引出了 ThreadLocal
# 例子可能会是这样
class ThreadSafeFormatter {
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
};
}
这个例子,确实是有用的,利用了 ThreadLocal 的什么能力呢?
实际上是数据线程绑定,得以让对象在整个线程内使用,请再次体会以下。
那么,使用 ThreadLocal 的这些场景,真正意图是什么呢?
实际上,它使得一个对象在整个线程生命周期内共享,而不用线程执行过程中的每个方法用到了都去 new 一个,可以在一定程度上复用对象。
另外,有一点比较重要的作用,可以使对象的传递更加优雅,对程序透明。比如,在事务方法内,我们并没有关心过事务对象的产生与传递,但是实际上框架就是通过 ThreadLocal 提供的能力,在整个线程执行中偷偷传递了,在生成的时候 set,在需要的时候 get。
你 Get 到了吗?
祝你打的愉快!😄