12 threadLocal的应用场景及问题

vvEcho 2025-03-06 17:15:44
Categories: Tags:

场景1,用于保存用户信息

例如在拦截器或者过滤器解析toke后,将用户信息保存到ThreadLocal中,这样在Controller中就可以直接获取到用户信息,而不用再从request中获取了。

场景2,SimpleDateFormat线程不安全

因为SimpleDateFormat不是线程安全的,所以如果多个线程同时使用SimpleDateFormat,可能会出现日期格式化错误。
根本原因在于SimpleDateFormat 内部通过一个Calendar对象维护格式化/解析的中间状态。

当多个线程同时调用 format() 或 parse() 时,Calendar的状态会被覆盖,导致:
数据错乱:输出字符串包含其他线程的时间片段(如秒数被覆盖)。
解析异常:生成非法时间字符串(如 “3030.3030”),无法转换回 Date。
校验失败:格式化前后的字符串不一致

1
private static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

threadLocal的问题

threadLocal是一个线程级别的容器,它有里面有一个静态变量threadLocalMap,threadLocalMap它内部是一个Entry数组,每个Entry是一个键值对,键是ThreadLocal对象,值是线程私有的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

public class WeakReference<T> extends Reference<T> {

/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}

/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}

}

问题出在它的key是弱引用,但value是强引用。若未调用remove(),线程池中复用的线程会导致value无法回收

被动清理触发条件:
ThreadLocalMap在调用set()、get()、remove()时,会被动扫描并清理key为null的Entry。若线程长期未执行这些操作,残留的value无法被回收

ThreadLocal对象因无外部强引用被回收(key变为null),value仍被Entry强引用,由于key变为null,后续线程无法获取对应的value,而这个value一直占用这块内存,导致内存泄漏