什么是 ThreadLocal
当你去银行办理业务时,你会看到很多柜台,当你在某个柜台办理业务时,该柜台的业务员可以单独为你办理业务,拿到你的资料信息,而其他柜台的业务员则为他的客户办理业务,拿到他的客户的资料信息。多个柜台的业务员独立地处理自己的业务,互不干扰。
而在Java中,业务员就是Thread,而你在银行办理业务时的资料信息就是ThreadLocal。
ThreadLocal 是 Java 中的一个线程本地存储类,它为每个线程提供了独立的变量副本。每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
打开 Thread 的源码,可以看到 Thread 类中有两个 ThreadLocal.ThreadLocalMap 类型的变量:
/*
* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals;
threadLocals 是当前线程的 ThreadLocalMap,而 inheritableThreadLocals 是当前线程的可继承的 ThreadLocalMap。
ThreadLocal的基础用法
public class ThreadLocalExample {
// 创建 ThreadLocal 变量
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 主线程设置值
threadLocal.set("主线程的值");
System.out.println("主线程获取值: " + threadLocal.get());
// 创建新线程
Thread thread1 = new Thread(() -> {
threadLocal.set("线程1的值");
System.out.println("线程1获取值: " + threadLocal.get());
});
Thread thread2 = new Thread(() -> {
threadLocal.set("线程2的值");
System.out.println("线程2获取值: " + threadLocal.get());
});
thread1.start();
thread2.start();
// 主线程的值不受影响
System.out.println("主线程最终值: " + threadLocal.get());
}
}
输出结果:
主线程获取值: 主线程的值
线程1获取值: 线程1的值
线程2获取值: 线程2的值
主线程最终值: 主线程的值
ThreadLocal的应用场景
ThreadLocal 有几个经典的应用场景:
- 数据库连接管理:每个线程都需要一个独立的数据库连接,而 ThreadLocal 可以确保每个线程都有自己的连接。
- 用户上下文信息:在 Web 应用中,每个请求都需要一个独立的用户上下文信息,而 ThreadLocal 可以确保每个请求都有自己的上下文信息。
- 日期格式化:SimpleDateFormat 是线程不安全的,而 ThreadLocal 可以确保每个线程都有自己的 SimpleDateFormat 对象。
- 随机数生成器:生成随机数时,每个线程都需要一个独立的随机数生成器。
数据库连接管理
这里给出一个简单的数据库连接管理示例:
public class DatabaseConnectionManager {
private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "password");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
public static void closeConnection() {
Connection conn = connectionHolder.get();
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// 处理异常
} finally {
connectionHolder.remove();
}
}
}
}
这里的示例真的比较简单,如果想了解一些连接池框架里是如何使用ThreadLocal的,建议去看看 HikariCP 的ConcurrentBag类,该类使用ThreadLocal( ThreadLocal<List<Object>> threadList)来缓存每个线程最近使用过的数据库连接,实现了线程隔离和连接复用。
用户上下文信息
在 Web 应用中,每个请求都需要一个独立的用户上下文信息,而 ThreadLocal 可以确保每个请求都有自己的上下文信息。
public class UserContext {
private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getCurrentUser() {
return userHolder.get();
}
public static void clear() {
userHolder.remove();
}
}
定义好 UserContext 之后,可以在Web应用中使用拦截器来设置用户上下文信息:
public class UserInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String userId = request.getHeader("User-Id");
if (userId!= null) {
User user = userService.findById(userId);
UserContext.setUser(user);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
UserContext.clear();
}
}
这里需要注意,要在afterCompletion方法中调用UserContext.clear()方法,而不要在postHandle方法中调用。
因为postHandle方法是在请求处理完成后调用的,afterCompletion方法在请求结束后调用的。
如果当请求发生异常时,postHandle方法不会被调用,因此在afterCompletion方法中调用UserContext.clear()方法是安全的。
日期格式化
我们知道 SimpleDateFormat 是线程不安全的,而 ThreadLocal 可以确保每个线程都有自己的 SimpleDateFormat 对象,从而达到线程安全。
public class DateFormatUtil {
private static final ThreadLocal<SimpleDateFormat> dateFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String format(Date date) {
return dateFormat.get().format(date);
}
public static Date parse(String dateStr) throws ParseException {
return dateFormat.get().parse(dateStr);
}
}
随机数生成器
public class ThreadLocalRandom {
private static final ThreadLocal<Random> random =
new ThreadLocal<Random>() {
@Override
protected Random initialValue() {
return new Random();
}
};
public static int nextInt(int bound) {
return random.get().nextInt(bound);
}
public static double nextDouble() {
return random.get().nextDouble();
}
}
其实在JDK中,也提供了线程安全的 java.util.concurrent.ThreadLocalRandom,它是通过Thread的threadLocalRandomSeed属性来保证线程安全的,实际应用时推荐 java.util.concurrent.ThreadLocalRandom。
ThreadLocal 源码分析
数据结构
ThreadLocal 的核心是 ThreadLocalMap,这是一个定制的哈希表,专门用于存储线程本地变量。
static class ThreadLocalMap {
// Entry 继承 WeakReference,key 是 ThreadLocal 的弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 初始容量
private static final int INITIAL_CAPACITY = 16;
// Entry 数组
private Entry[] table;
// 数组大小
private int size = 0;
// 扩容阈值
private int threshold;
}
每个 Thread 对象都包含一个 ThreadLocalMap:
public class Thread implements Runnable {
// ThreadLocal 相关的 Map
ThreadLocal.ThreadLocalMap threadLocals = null;
// InheritableThreadLocal 相关的 Map
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
Thread中通过threadLocals和inheritableThreadLocals来存储线程本地变量,而这两者都是ThreadLocal中的ThreadLocalMap。
ThreadLocalMap中主要是通过Entry数组来存储信息的,Entry 的 key 是 ThreadLocal 本身,而value是实际存储的线程本地变量信息。

Entry中 key 是一个弱引用,value 是一个强引用。
这里说一下强软弱虚引用:
- 强引用:只要强引用存在,对象绝不会被GC回收,即使内存不足时JVM会抛出OutOfMemoryError而非回收对象
- 软引用:通过SoftReference类实现,内存不足时才会被回收,适合实现内存敏感缓存
- 弱引用:通过WeakReference类实现,对象只能存活到下一次GC,无论内存是否充足。
- 虚引用:通过PhantomReference类实现,必须配合ReferenceQueue使用。无法通过get()获取对象,仅用于跟踪回收活动。
由于ThreadLoalMap的key是一个弱引用,当没有强引用指向 ThreadLocal 时, Entry 的 key 会成为 null,被 GC 回收。
核心方法
set() 方法
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果 map 存在,直接设置值
// 在 set 方法中会通过cleanSomeSlots来清理过期的 Entry
// 这里的过期是指 key 为 null 的 Entry
map.set(this, value);
else
// 如果 map 不存在,创建 map 并设置值
createMap(t, value);
}
// 获取线程的 ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建 ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get() 方法
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 从 map 中获取 Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果没有找到,返回初始值
return setInitialValue();
}
// 设置初始值
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// 初始值方法,可以被重写
protected T initialValue() {
return null;
}
remove() 方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
//获取entry数组
Entry[] tab = table;
//长度
int len = tab.length;
//通过key计算索引
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//当前引用对象所引用的目标对象(referent)是否与参数obj相同。
if (e.refersTo(key)) {
//清除当前entry,expungeStaleEntry同时也会清理key为null的元素
e.clear();
expungeStaleEntry(i);
return;
}
}
}
ThreadLocalMap 的哈希冲突解决
ThreadLocalMap 使用开放地址法(线性探测)解决哈希冲突:
private void set(ThreadLocal<?> key, Object value) {
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
// 找到相同的 key,更新 value
e.value = value;
return;
}
if (k == null) {
// key 被 GC 回收,替换过期的 Entry
replaceStaleEntry(key, value, i);
return;
}
}
// 没有找到,创建新的 Entry
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
常见的哈希冲突解决思路
哈希冲突是哈希表实现中的核心问题,当不同的键计算出相同的哈希值时就会发生冲突。主要有以下几种解决方案:
1. 链地址法(Separate Chaining) 在每个哈希桶中维护一个链表或其他数据结构,冲突的元素都存储在同一个桶的链表中,HashMap 使用的就是这种方法。
HashMap 使用链地址法 + 红黑树的混合策略:
// HashMap 的核心数据结构
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; // 链表指针
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
// 红黑树节点
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
// ...
}
HashMap 冲突解决流程:
计算哈希值:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
定位桶位置:
// 通过哈希值和数组长度计算桶索引
int index = (n - 1) & hash; // n 是数组长度,必须是 2 的幂
处理冲突:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 计算桶索引
if ((p = tab[i = (n - 1) & hash]) == null)
// 桶为空,直接插入
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
// key 相同,更新值
e = p;
else if (p instanceof TreeNode)
// 红黑树节点,使用树的插入方法
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 链表节点,遍历链表
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 链表末尾,插入新节点
p.next = newNode(hash, key, value, null);
// 链表长度达到阈值,转换为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // TREEIFY_THRESHOLD = 8
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 更新已存在的 key
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// ...
}
HashMap 的优化策略:
- 链表转红黑树:当链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转换为红黑树
- 红黑树转链表:当红黑树节点数 ≤ 6 时,转换回链表
- 扰动函数:
hash ^ (hash >>> 16)减少哈希冲突 - 负载因子:默认 0.75,平衡时间和空间复杂度
2. 开放地址法(Open Addressing) 当发生冲突时,按照某种规则寻找下一个空闲位置,包括线性探测、二次探测、双重哈希等,ThreadLocalMap 使用的是线性探测。
ThreadLocal 的哈希算法:
public class ThreadLocal<T> {
// 魔数,黄金分割比例的整数表示
private static final int HASH_INCREMENT = 0x61c88647;
// 全局哈希码生成器
private static AtomicInteger nextHashCode = new AtomicInteger();
// 每个 ThreadLocal 实例的哈希码
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
这个魔数 0x61c88647 是黄金分割比例 (√5 - 1) / 2 的整数表示,能够在 2 的幂次方长度的数组中产生非常均匀的分布,大大减少哈希冲突。
为什么 ThreadLocalMap 选择线性探测?
- 冲突较少:ThreadLocal 的哈希算法设计良好,冲突概率低
- 内存效率:无需额外的指针存储,节省内存
- 缓存友好:连续内存访问,CPU 缓存命中率高
- 实现简单:代码简洁,易于维护
ThreadLocalMap vs HashMap 冲突解决对比
| 特性 | ThreadLocalMap | HashMap |
|---|---|---|
| 冲突解决 | 开放地址法(线性探测) | 链地址法 + 红黑树 |
| 时间复杂度 | O(1) ~ O(n) | O(1) ~ O(log n) |
| 空间利用率 | 高(无额外指针开销) | 中等(需要额外指针) |
| 实现复杂度 | 简单 | 复杂 |
| 适用场景 | 冲突较少,内存敏感 | 通用场景,冲突较多 |
3. 再哈希法(Rehashing) 使用多个哈希函数,当一个发生冲突时使用下一个,实现复杂,但冲突概率低
4. 建立公共溢出区 将冲突的元素存储在单独的溢出区域,适用于冲突较少的场景
ThreadLocal 使用注意事项
内存泄漏问题
问题原因
- 弱引用机制:ThreadLocalMap 的 Entry 继承 WeakReference,key(ThreadLocal)是弱引用
- value 强引用:Entry 的 value 是强引用
- 生命周期差异:线程生命周期可能比 ThreadLocal 对象长

内存泄漏场景
public class MemoryLeakExample {
public static void main(String[] args) {
ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
// 设置大对象
threadLocal.set(new byte[1024 * 1024 * 10]); // 10MB
// ThreadLocal 对象被回收,但 value 仍然被引用
threadLocal = null;
System.gc();
// 此时 ThreadLocalMap 中的 Entry 的 key 为 null,但 value 仍然存在
// 如果线程不结束,这 10MB 内存就无法回收
// 正确做法:手动清理
// threadLocal.remove();
}
}
解决方案
- 手动清理:
public class SafeThreadLocalUsage {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void doSomething() {
try {
threadLocal.set("some value");
// 业务逻辑
} finally {
// 确保清理
threadLocal.remove();
}
}
}
- 使用 try-with-resources 模式:
public class AutoCleanThreadLocal<T> implements AutoCloseable {
private final ThreadLocal<T> threadLocal = new ThreadLocal<>();
public void set(T value) {
threadLocal.set(value);
}
public T get() {
return threadLocal.get();
}
@Override
public void close() {
threadLocal.remove();
}
}
// 使用方式
public void example() {
try (AutoCleanThreadLocal<String> autoClean = new AutoCleanThreadLocal<>()) {
autoClean.set("value");
// 业务逻辑
} // 自动调用 close() 方法
}
线程池中的问题
问题描述
线程池中的线程会被复用,ThreadLocal 变量可能会在不同任务间产生数据污染:
public class ThreadPoolProblem {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static final ExecutorService executor = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
// 任务1:设置值但不清理
executor.submit(() -> {
threadLocal.set("任务1的值");
System.out.println("任务1: " + threadLocal.get());
// 没有调用 remove()
});
// 任务2:可能获取到任务1的值
executor.submit(() -> {
System.out.println("任务2: " + threadLocal.get()); // 可能输出 "任务1的值"
});
executor.shutdown();
}
}
解决方案
- 任务包装器:
public class ThreadLocalTaskWrapper implements Runnable {
private final Runnable task;
private final List<ThreadLocal<?>> threadLocals;
public ThreadLocalTaskWrapper(Runnable task, ThreadLocal<?>... threadLocals) {
this.task = task;
this.threadLocals = Arrays.asList(threadLocals);
}
@Override
public void run() {
try {
task.run();
} finally {
// 清理所有 ThreadLocal
threadLocals.forEach(ThreadLocal::remove);
}
}
}
- 自定义线程池:
public class ThreadLocalAwareThreadPool extends ThreadPoolExecutor {
public ThreadLocalAwareThreadPool(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 清理当前线程的所有 ThreadLocal
clearThreadLocals();
}
private void clearThreadLocals() {
try {
Thread currentThread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
threadLocalsField.set(currentThread, null);
Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals");
inheritableThreadLocalsField.setAccessible(true);
inheritableThreadLocalsField.set(currentThread, null);
} catch (Exception e) {
// 处理异常
}
}
}
InheritableThreadLocal
InheritableThreadLocal 是 ThreadLocal 的子类,它允许子线程继承父线程的 ThreadLocal 值。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
使用示例
public class InheritableThreadLocalExample {
private static final InheritableThreadLocal<String> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) {
// 父线程设置值
inheritableThreadLocal.set("父线程的值");
System.out.println("父线程: " + inheritableThreadLocal.get());
// 创建子线程
Thread childThread = new Thread(() -> {
// 子线程可以获取父线程的值
System.out.println("子线程继承的值: " + inheritableThreadLocal.get());
// 子线程修改值
inheritableThreadLocal.set("子线程修改的值");
System.out.println("子线程修改后: " + inheritableThreadLocal.get());
});
childThread.start();
try {
childThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 父线程的值不受影响
System.out.println("父线程最终值: " + inheritableThreadLocal.get());
}
}
输出结果:
父线程: 父线程的值
子线程继承的值: 父线程的值
子线程修改后: 子线程修改的值
父线程最终值: 父线程的值
需要注意的是,只有在子线程创建的时候才会从父线程继承值,之后子线程的值修改不会影响父线程,父线程值的修改也不会影响子线程。
从而引申出一个问题:线程池中的线程基本都是复用的,不会进行创建,所以在线程池中InheritableThreadLocal可能会失效。
自定义继承逻辑
public class CustomInheritableThreadLocal extends InheritableThreadLocal<Map<String, String>> {
@Override
protected Map<String, String> childValue(Map<String, String> parentValue) {
// 深拷贝父线程的 Map
return parentValue != null ? new HashMap<>(parentValue) : new HashMap<>();
}
}
// 使用示例
public class CustomInheritanceExample {
private static final CustomInheritableThreadLocal contextMap =
new CustomInheritableThreadLocal();
public static void main(String[] args) {
// 父线程设置上下文
Map<String, String> parentContext = new HashMap<>();
parentContext.put("userId", "123");
parentContext.put("requestId", "req-456");
contextMap.set(parentContext);
Thread childThread = new Thread(() -> {
Map<String, String> childContext = contextMap.get();
System.out.println("子线程继承的上下文: " + childContext);
// 子线程添加新的上下文信息
childContext.put("childThreadId", Thread.currentThread().getName());
System.out.println("子线程修改后的上下文: " + childContext);
});
childThread.start();
try {
childThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 父线程的上下文不受影响
System.out.println("父线程的上下文: " + contextMap.get());
}
}
InheritableThreadLocal 的限制
- 线程池问题:线程池中的线程复用会导致继承关系混乱
- 性能开销:每次创建线程都需要复制父线程的 InheritableThreadLocal
- 内存占用:子线程会持有父线程数据的副本
线程池中使用 ThreadLocal 的最佳实践
TransmittableThreadLocal(TTL)
阿里巴巴开源的 TTL 库解决了线程池中 ThreadLocal 传递的问题:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
示例:
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
public class TTLExample {
private static final TransmittableThreadLocal<String> context =
new TransmittableThreadLocal<>();
public static void main(String[] args) {
// 包装线程池
ExecutorService executor = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(2));
// 主线程设置上下文
context.set("主线程上下文");
// 提交任务到线程池
executor.submit(() -> {
// 可以正确获取主线程的上下文
System.out.println("线程池任务获取上下文: " + context.get());
});
executor.shutdown();
}
}
手动传递上下文
public class ManualContextPassing {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static class ContextAwareTask implements Runnable {
private final String context;
private final Runnable task;
public ContextAwareTask(String context, Runnable task) {
this.context = context;
this.task = task;
}
@Override
public void run() {
String oldContext = threadLocal.get();
try {
threadLocal.set(context);
task.run();
} finally {
if (oldContext != null) {
threadLocal.set(oldContext);
} else {
threadLocal.remove();
}
}
}
}
public static void submitTask(ExecutorService executor, Runnable task) {
String currentContext = threadLocal.get();
executor.submit(new ContextAwareTask(currentContext, task));
}
}
ThreadLocal 性能优化
使用 withInitial() 方法
Java 8 引入了更简洁的初始化方式:
// 传统方式
private static final ThreadLocal<SimpleDateFormat> dateFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
// Java 8 方式
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
减少 ThreadLocal 实例数量
// 不好的做法:多个 ThreadLocal 实例
public class BadPractice {
private static final ThreadLocal<String> userId = new ThreadLocal<>();
private static final ThreadLocal<String> requestId = new ThreadLocal<>();
private static final ThreadLocal<String> sessionId = new ThreadLocal<>();
}
// 好的做法:使用一个 ThreadLocal 存储上下文对象
public class GoodPractice {
private static final ThreadLocal<RequestContext> context = new ThreadLocal<>();
public static class RequestContext {
private String userId;
private String requestId;
private String sessionId;
// getters and setters
}
}
预分配容量
public class OptimizedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<T> supplier;
public OptimizedThreadLocal(Supplier<T> supplier) {
this.supplier = supplier;
}
@Override
protected T initialValue() {
return supplier.get();
}
// 预热方法
public void warmUp() {
get(); // 触发初始化
}
}
总结
ThreadLocal 的优势
- 线程安全:无需同步机制,天然线程安全
- 性能优秀:避免了锁竞争,提高并发性能
- 使用简单:API 简洁,易于理解和使用
- 内存隔离:每个线程拥有独立的变量副本
ThreadLocal 的劣势
- 内存泄漏风险:需要手动清理,否则可能导致内存泄漏
- 线程池兼容性:在线程池环境下需要特殊处理
- 调试困难:线程本地变量增加了调试复杂度
- 内存开销:每个线程都需要存储变量副本
适用场景
- Web 应用:存储用户会话信息、请求上下文
- 数据库连接:管理线程级别的数据库连接
- 日期格式化:避免 SimpleDateFormat 的线程安全问题
- 随机数生成:为每个线程提供独立的随机数生成器
- 事务管理:存储事务上下文信息
- 分布式链路追踪中的TraceId传递
- Spring Security中的SecurityContext存储
- 多数据源切换场景
最佳实践
- 及时清理:使用完毕后立即调用
remove()方法 - 异常安全:在
finally块中进行清理 - 线程池注意:在线程池环境下使用 TTL 或手动传递上下文
- 合理使用:避免存储大对象,减少内存占用
- 监控内存:定期检查内存使用情况,防止内存泄漏
文章标签
冬眠
博主专注于技术、阅读与思考。在这里记录学习、思考与生活。
第 1 篇,共 3 篇
