一、ThreadLocal
ThreadLocal
是本地线程变量,其中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal
为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本,通过统一的操作方式,实现了不同线程使用不通的变量,使用起来比较方便。
Thread_1 --------> var_1
Thread_2 --------> var_2
Thread_3 --------> var_3
...
二、ThreadLocal怎么用
public class Main {
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
IntStream.range(0, 10).forEach(i -> {
new Thread(() -> {
threadLocal.set(i);
// 业务逻辑...
System.out.println(Thread.currentThread().getName() + "threadLocal var: " + threadLocal.get());
}).start();
});
}
}
/**
* Thread-0threadLocal var: 0
* Thread-4threadLocal var: 4
* Thread-3threadLocal var: 3
* Thread-2threadLocal var: 2
* Thread-1threadLocal var: 1
*/
可以看到在不同的线程中通过访问同一变量threadLocal,而获取到的是不同的值。
在一个应用系统中,一般是通过创建一个线程去负责处理业务逻辑,而业务逻辑的处理过程,通常需要跨越多个方法,多个方法之间可能需要相同的参数,我们称为context上下文信息,如果方法之间来回传递这个context是比较麻烦的,而ThreadLocal很好地解决了这个问题。
从实际应用的角度,ThreadLocal
可以:
- 在进行对象跨层传递的时候,使用
ThreadLocal
可以避免多次传递,打破层次间的约束。 - 线程间数据隔离
- 进行事务操作,用于存储线程事务信息。
- 数据库连接,
Session
会话管理。
三、ThreadLocal的实现原理
Thread类中有两个变量:
- threadLocals
- inheritableThreadLocals
二者的类型都是ThreadLocal的内部类ThreadLocalMap类型,其类似HashMap,默认情况下二者都是null。当线程第一次调用ThreadLocal的get或set方法时会创建它们。
/---------------------------\ /-----------------------\
| | | |
| Thread | | ThreadLocalMap |
| | | |
|1. threadLocals | | Entry |
|2. inheritableThreadLocals | | key | value |
| | | tl | var |
| | | |
\---------------------------/ \-----------------------/
ThreadLocal的set()方法:
public void set(T value) {
// 获取当前线程,称为调用者线程
Thread t = Thread.currentThread();
// 以调用者线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
// 如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
if (map != null)
map.set(this, value);
// 如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
// 获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get()方法:
public T get() {
// 获取调用者线程
Thread t = Thread.currentThread();
// 获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
// 如果threadLocals变量不为null,就可以在map中查找到本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
return setInitialValue();
}
private T setInitialValue() {
//protected T initialValue() {return null;}
T value = initialValue();
// 获取调用者线程
Thread t = Thread.currentThread();
// 以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
// 如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this, value);
// 如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
return value;
}
所以在不同的线程中通过get()方法获取threadLocal变量,本质上是从当前thread的threaLocals存的变量。
四、避免内存泄漏
在第三节介绍了每个Thread都维护了一个ThreadLocalMap,key为ThreadLocal实例,为弱引用,value为线程变量的副本,他们的引用关系:
-------------------------------------Heap-----------------------\
/-----------Stack-----------\ | ------------\ |
| | | | | |
| | | v | |
| ThreadLocalRef --------------------|-----> ThreadLocal | /-------------------\ |
| | | | | ThreadLocalMap | |
| CurrentTreadRef --------------------|-----> CurrentThread ------|-----> | Entry | |
| | | | | key | value | |
| | | | | tl | var | |
\---------------------------/ | | | | | | |
| | \--------|------|---/ |
| \----------------/ | |
\---------------------------------------------------------------/
|
v
线程变量val
从它们的引用关系可以看出来,threadLocalMap中的key为ThreadLocal的弱引用,当ThreadLocal在其他地方不存在强引用时,key(ThreadLocal)势必会被GC回收,这样key变成了null。而value还存在着强引用不会被GC回收,只有当当前进程结束这个强引用才会断开,但如果线程迟迟不结束,这个强引用就会一直存在,value就会一直存在内存中,造成内存泄漏。
因此如果threadlocal不再使用时,通过remove()方法删除,避免内存泄漏的情况发生。