ThreadLocal
基础概念:IT-BLOG-CN
ThreadLocal
是Java
中用于在同一个线程中存储和隔离变量的一种机制。通常情况下,我们使用ThreadLocal
来存储线程独有的变量,并在任务完成后通过remove
方法清理这些变量,以防止内存泄漏。然而,在使用线程池时,线程会被重用,这可能导致ThreadLocal
变量未被及时清理,从而引发内存泄漏问题。
除了直接调用ThreadLocal
的remove
方法外,还有一些其他方式可以帮助释放ThreadLocal
变量:
一、在线程池中使用自定义的ThreadFactory
创建一个自定义的ThreadFactory
,在创建线程时添加钩子,以便在任务完成后清理ThreadLocal
变量。扩展:搭建统一线程池平台,对该部分进行了改造。提供多个工厂,就包含自动清理工厂。
java
import java.util.concurrent.ThreadFactory;
public class CleaningThreadFactory implements ThreadFactory {
private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
return defaultFactory.newThread(() -> {
try {
r.run();
} finally {
// 清理ThreadLocal变量
ThreadLocalHolder.clear();
}
});
}
}
这里的ThreadLocalHolder
就是所有ThreadLocal
的一个管理类,这里举个例子:
java
public class ThreadLocalHolder {
private static final ThreadLocal<AggAlibabaRerQueryResponse> TL_AGG_REF_RER = new ThreadLocal<>();
private static final ThreadLocal<OpenAlibabaSearchResponse> TL_ORDER_DETAIL = new ThreadLocal<>();
// get/set 只流一个参考
public static void setAggRefRer(AggAlibabaRerQueryResponse aggRefRer) {
TL_AGG_REF_RER.set(aggRefRer);
}
public static FlightRefRerQueryResponse getFlightRefRer() {
return TL_FLIGHT_REF_RER.get();
}
/**
* 用于清空threadlocal,否则会有内存泄漏
*/
public static void clear() {
TL_AGG_REF_RER.remove();
TL_ORDER_DETAIL.remove();
}
二、使用ThreadPoolExecutor
的钩子方法
可以扩展ThreadPoolExecutor
并覆盖其beforeExecute
和afterExecute
方法,以便在任务执行前后进行清理操作。
java
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;
public class CleaningThreadPoolExecutor extends ThreadPoolExecutor {
public CleaningThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, LinkedBlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
// 清理之前的ThreadLocal变量
ThreadLocalHolder.clear();
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 清理当前的ThreadLocal变量
ThreadLocalHolder.clear();
}
}
三、使用装饰器模式包装Runnable
和Callable
可以创建一个装饰器,包装Runnable
和Callable
任务,在任务执行前后进行清理操作。
java
import java.util.concurrent.Callable;
public class CleaningRunnable implements Runnable {
private final Runnable task;
public CleaningRunnable(Runnable task) {
this.task = task;
}
@Override
public void run() {
try {
task.run();
} finally {
// 清理ThreadLocal变量
ThreadLocalHolder.clear();
}
}
}
public class CleaningCallable<V> implements Callable<V> {
private final Callable<V> task;
public CleaningCallable(Callable<V> task) {
this.task = task;
}
@Override
public V call() throws Exception {
try {
return task.call();
} finally {
// 清理ThreadLocal变量
ThreadLocalHolder.clear();
}
}
}
四、使用ThreadLocal
的子类
可以创建一个ThreadLocal
的子类,并在任务完成后自动清理变量。可以通过覆盖initialValue
方法来实现:finalize
出发的时机是在gc
的时候,但是finalize
方法在现代Java
开发中并不推荐使用,因为它的执行时间和执行顺序是不确定的。
java
public class AutoCleanupThreadLocal<T> extends ThreadLocal<T> {
@Override
protected void finalize() throws Throwable {
this.remove();
super.finalize();
}
}
五、TheadLocal 实际使用案例
将整个流程中需要用到的接口数据都存储起来,这个流程中调用链路比较深,同时也存在并发的操作,可以使用ThreadLocal
java
public class ThreadLocalHolder {
private static final ThreadLocal<AggAlibabaRerQueryResponse> TL_AGG_REF_RER = new ThreadLocal<>();
private static final ThreadLocal<OpenAlibabaSearchResponse> TL_ORDER_DETAIL = new ThreadLocal<>();
private static final ThreadLocal<FlightAlibabaResponse> TL_FLIGHT_REF_RER = new ThreadLocal<>();
private static final ThreadLocal<XOrderAlibabaInfo> TL_X_ORDER_DETAIL = new ThreadLocal<>();
private static final ThreadLocal<FlightAlibabaResponseBodyType> TL_DOM_FLIGHT_SEARCH_RESULT = new ThreadLocal<>();
private static final ThreadLocal<ResponseAlibabaType> TL_RESCHEDULE_FLIGHT_SEARCH_RESULT = new ThreadLocal<>();
// get/set 只流一个参考
public static void setAggRefRer(AggAlibabaRerQueryResponse aggRefRer) {
TL_AGG_REF_RER.set(aggRefRer);
}
public static FlightRefRerQueryResponse getFlightRefRer() {
return TL_FLIGHT_REF_RER.get();
}
/**
* 用于清空threadlocal,否则会有内存泄漏
*/
public static void clear() {
TL_AGG_REF_RER.remove();
TL_ORDER_DETAIL.remove();
TL_FLIGHT_REF_RER.remove();
TL_X_ORDER_DETAIL.remove();
TL_DOM_FLIGHT_SEARCH_RESULT.remove();
TL_RESCHEDULE_FLIGHT_SEARCH_RESULT.remove();
}
六、基础支持补充 ----- ThreadLocal 的实现原理
下面是ThreadLocal
的类图结构,从图中可知:Thread
类中有两个变量threadLocals
和inheritableThreadLocals
,二者都是ThreadLocal
内部类ThreadLocalMap
类型的变量,我们通过查看内部类ThreadLocalMap
可以发现实际上它类似于一个HashMap
。在默认情况下,每个线程中的这两个变量都为null
。
java
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
只有当线程第一次调用ThreadLocal
的set
或者get
方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,每个线程的本地变量不是存放在ThreadLocal
实例中,而是放在调用线程的ThreadLocals
变量里面(前面也说过,该变量是Thread
类的变量)。也就是说,ThreadLocal
类型的本地变量是存放在具体的线程空间上,相当于一个装载本地变量的工具壳,通过set
方法将value
添加到调用线程的threadLocals
中,当调用线程调用get
方法时候能够从它的threadLocals
中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals
中,所以不使用本地变量的时候需要调用remove
方法将threadLocals
中删除不用的本地变量。下面我们通过查看ThreadLocal
的set
、get
以及remove
方法来查看ThreadLocal
具体实怎样工作的。
【1】set方法源码
java
public void set(T value) {
//(1)获取当前线程(调用者线程)
Thread t = Thread.currentThread();
//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this, value);
//(4)如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
在上面的代码中,(2)处调用getMap
方法获得当前线程对应的threadLocals
(参照上面的图示和文字说明),该方法代码如下:
java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}
如果调用getMap
方法返回值不为null
,就直接将value
值设置到threadLocals
中(key
为当前线程引用,值为本地变量);如果getMap
方法返回null
说明是第一次调用set
方法(前面说到过,threadLocals
默认值为null
,只有调用set
方法的时候才会创建map
),这个时候就需要调用createMap
方法创建 threadLocals
,该方法如下所示:createMap
方法不仅创建了threadLocals
,同时也将要添加的本地变量值添加到了threadLocals
中。
java
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
【2】get
方法源码: 在get
方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals
不为null
,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue
方法初始化threadLocals
变量。在setInitialValue
方法中,类似于set
方法的实现,都是判断当前线程的threadLocals
变量是否为null
,是则添加本地变量(这个时候由于是初始化,所以添加的值为null
),否则创建threadLocals
变量,同样添加的值为null
。
java
public T get() {
//(1)获取当前线程
Thread t = Thread.currentThread();
//(2)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//(4)执行到此处,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;
}
【3】remove
方法的实现: remove
方法判断当前线程对应的threadLocals
变量是否为null
,不为null
就直接删除当前线程中指定的threadLocals
变量。
java
public void remove() {
//获取当前线程绑定的threadLocals
ThreadLocalMap m = getMap(Thread.currentThread());
//如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.refersTo(key)) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
【4】如下图所示: 每个线程内部有一个名为threadLocals
的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap
类型(类似于一个HashMap
),其中的key
为当前定义的ThreadLocal
变量的this
引用,value
为我们使用set
方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals
中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(可能会导致内存溢出),因此使用完毕需要将其remove
掉。