【Java并发】ThreadLocal原理与最佳实践
ThreadLocal是Java中实现线程隔离的重要工具,它为每个线程提供独立的变量副本,避免了线程安全问题。本文深入剖析ThreadLocal的实现原理、内存泄漏问题及最佳实践。
什么是ThreadLocal
ThreadLocal提供线程局部变量,每个访问该变量的线程都有自己独立的副本,互不干扰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ┌─────────────────────────────────────────────────────────────────┐ │ ThreadLocal工作方式 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 普通变量(共享): │ │ │ │ Thread-1 ────┐ │ │ ├────▶ 共享变量 ◀──── 需要同步! │ │ Thread-2 ────┘ │ │ │ │ │ │ ThreadLocal(隔离): │ │ │ │ Thread-1 ────────▶ 副本1 ┐ │ │ ├── 各自独立,无需同步 │ │ Thread-2 ────────▶ 副本2 ┘ │ │ │ └─────────────────────────────────────────────────────────────────┘
|
基本使用
创建和使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Hello");
String value = threadLocal.get();
threadLocal.remove();
String value2 = threadLocal.get();
|
带初始值的ThreadLocal
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
ThreadLocal<SimpleDateFormat> dateFormat2 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
SimpleDateFormat sdf = dateFormat.get();
|
多线程验证隔离性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class ThreadLocalDemo { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) { new Thread(() -> { threadLocal.set(100); System.out.println("Thread-1: " + threadLocal.get()); threadLocal.remove(); }).start();
new Thread(() -> { threadLocal.set(200); System.out.println("Thread-2: " + threadLocal.get()); threadLocal.remove(); }).start();
threadLocal.set(300); System.out.println("Main: " + threadLocal.get()); threadLocal.remove(); } }
|
底层原理
整体结构
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
| ┌─────────────────────────────────────────────────────────────────────┐ │ ThreadLocal存储结构 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ Thread对象 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ threadLocals: ThreadLocalMap │ │ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ │ │ Entry[] table ││ │ │ │ │ ┌─────────────┬─────────────┬─────────────┬──────────┐││ │ │ │ │ │ Entry[0] │ Entry[1] │ Entry[2] │ ... │││ │ │ │ │ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ │││ │ │ │ │ │ │key(弱引用)│ │key(弱引用)│ │key(弱引用)│ │ │││ │ │ │ │ │ │ ↓ │ │ │ ↓ │ │ │ ↓ │ │ │││ │ │ │ │ │ │ThreadLoc│ │ │ThreadLoc│ │ │ThreadLoc│ │ │││ │ │ │ │ │ │al实例A │ │ │al实例B │ │ │al实例C │ │ │││ │ │ │ │ │ ├─────────┤ │ ├─────────┤ │ ├─────────┤ │ │││ │ │ │ │ │ │ value │ │ │ value │ │ │ value │ │ │││ │ │ │ │ │ │ "数据A" │ │ │ "数据B" │ │ │ "数据C" │ │ │││ │ │ │ │ │ └─────────┘ │ └─────────┘ │ └─────────┘ │ │││ │ │ │ │ └─────────────┴─────────────┴─────────────┴──────────┘││ │ │ │ └─────────────────────────────────────────────────────────┘│ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 注意:数据存储在Thread中,而不是ThreadLocal中 │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
Thread类中的字段
1 2 3 4 5 6 7
| public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; }
|
ThreadLocalMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value;
Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
private Entry[] table;
private static final int INITIAL_CAPACITY = 16;
private int threshold; }
|
set方法源码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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; }
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
|
get方法源码分析
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
| public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { 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; }
|
哈希冲突解决:开放地址法
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 36 37 38 39
| 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) { e.value = value; return; }
if (k == null) { replaceStaleEntry(key, value, i); return; } }
tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
|
神奇的哈希码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ThreadLocal<T> { private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } }
|
1 2 3 4 5 6 7 8 9 10
| 0x61c88647 是黄金分割数的整数形式
生成的哈希值序列: 0x00000000 0x61c88647 0xc3910c8e 0x2559d2d5 ...
这个序列在2的幂次大小的数组中分布非常均匀
|
内存泄漏问题
弱引用的设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ┌─────────────────────────────────────────────────────────────────────┐ │ Entry的引用关系 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 栈 堆 │ │ ┌──────┐ │ │ │ ref │─────强引用────▶ ThreadLocal实例 │ │ └──────┘ ▲ │ │ │ │ │ 弱引用 │ │ │ │ │ Thread ───▶ ThreadLocalMap ───▶ Entry │ │ │ │ │ 强引用 │ │ │ │ │ ▼ │ │ Value │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
为什么Entry的key是弱引用?
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
| 假设Entry的key是强引用:
ref ─────────────▶ ThreadLocal实例 ◀───────┐ │ ▲ │ │ │ 强引用 ref = null │ │ │ ThreadLocalMap ───▶ Entry─┘ ▼ ▲ 引用断开 Thread
问题:即使ref=null,ThreadLocal实例仍被Entry强引用 只要线程存活,ThreadLocal就无法被GC 特别是在线程池场景下,线程会被复用,导致内存泄漏
使用弱引用后:
ref ─────────────▶ ThreadLocal实例 ◀───────┐ │ ▲ │ │ │ 弱引用 ref = null │ │ │ ThreadLocalMap ───▶ Entry─┘ ▼ ▲ 引用断开 Thread
GC时:ThreadLocal实例只有弱引用,可以被回收 Entry的key变为null
|
但Value仍然泄漏!
1 2 3 4 5 6 7 8 9
| GC回收ThreadLocal后:
ThreadLocalMap ───▶ Entry │ key = null(已被GC) │ value ───▶ 实际数据(强引用!)
问题:Entry还在,value还被强引用,无法GC
|
内存泄漏示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class MemoryLeakDemo { private static ExecutorService executor = Executors.newFixedThreadPool(1);
public static void main(String[] args) throws Exception { for (int i = 0; i < 1000; i++) { executor.execute(() -> { ThreadLocal<byte[]> local = new ThreadLocal<>(); local.set(new byte[1024 * 1024]); }); } } }
|
ThreadLocalMap的清理机制
ThreadLocalMap在操作时会主动清理key为null的Entry:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
private void set(ThreadLocal<?> key, Object value) { replaceStaleEntry(key, value, i); }
private void rehash() { expungeStaleEntries(); }
|
但这种清理是被动的、不彻底的,最佳实践仍然是手动调用remove()。
正确使用方式
使用try-finally确保清理
1 2 3 4 5 6 7 8 9 10 11
| private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
public void doSomething() { try { userHolder.set(getCurrentUser()); processRequest(); } finally { userHolder.remove(); } }
|
封装成工具类
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
| public class UserContext { private static final ThreadLocal<User> USER_HOLDER = new ThreadLocal<>();
public static void setUser(User user) { USER_HOLDER.set(user); }
public static User getUser() { return USER_HOLDER.get(); }
public static void clear() { USER_HOLDER.remove(); }
public static void runWithUser(User user, Runnable task) { try { setUser(user); task.run(); } finally { clear(); } }
public static <T> T callWithUser(User user, Callable<T> task) throws Exception { try { setUser(user); return task.call(); } finally { clear(); } } }
|
InheritableThreadLocal
普通ThreadLocal在子线程中无法获取父线程的值:
1 2 3 4 5 6
| ThreadLocal<String> local = new ThreadLocal<>(); local.set("父线程的值");
new Thread(() -> { System.out.println(local.get()); }).start();
|
InheritableThreadLocal可以实现父子线程传递:
1 2 3 4 5 6
| InheritableThreadLocal<String> local = new InheritableThreadLocal<>(); local.set("父线程的值");
new Thread(() -> { System.out.println(local.get()); }).start();
|
原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Thread { ThreadLocal.ThreadLocalMap threadLocals; ThreadLocal.ThreadLocalMap inheritableThreadLocals;
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { Thread parent = currentThread(); if (inheritThreadLocals && parent.inheritableThreadLocals != null) { this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } } }
|
线程池场景的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| InheritableThreadLocal<String> local = new InheritableThreadLocal<>(); ExecutorService executor = Executors.newFixedThreadPool(1);
local.set("任务1的值"); executor.execute(() -> { System.out.println(local.get()); });
Thread.sleep(100);
local.set("任务2的值"); executor.execute(() -> { System.out.println(local.get()); });
|
TransmittableThreadLocal
阿里开源的TransmittableThreadLocal(TTL)解决了线程池场景的传递问题。
引入依赖
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.14.2</version> </dependency>
|
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.threadpool.TtlExecutors;
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
ExecutorService executor = TtlExecutors.getTtlExecutorService( Executors.newFixedThreadPool(1) );
context.set("任务1的值"); executor.execute(() -> { System.out.println(context.get()); });
Thread.sleep(100);
context.set("任务2的值"); executor.execute(() -> { System.out.println(context.get()); });
|
TTL原理
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
| ┌─────────────────────────────────────────────────────────────────────┐ │ TransmittableThreadLocal原理 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 提交任务时: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 1. 捕获(capture)当前线程的所有TTL值 │ │ │ │ 2. 将TTL值和原始Runnable包装成TtlRunnable │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ 执行任务时: │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 1. 备份(backup)执行线程当前的TTL值 │ │ │ │ 2. 重放(replay)捕获的TTL值到执行线程 │ │ │ │ 3. 执行原始Runnable │ │ │ │ 4. 恢复(restore)备份的TTL值 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ 父线程 子线程(线程池复用) │ │ ┌────────────┐ ┌────────────┐ │ │ │ TTL = "A" │──capture()──▶│ 保存"A" │ │ │ └────────────┘ │ │ │ │ │ backup() │ 备份原有值 │ │ │ replay() │ 设置"A" │ │ │ run() │ 执行任务 │ │ │ restore() │ 恢复原有值 │ │ └────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
实际应用场景
1. 用户上下文传递
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 36 37 38 39 40 41 42 43 44 45
| public class UserContextHolder { private static final ThreadLocal<UserContext> HOLDER = new ThreadLocal<>();
public static void set(UserContext context) { HOLDER.set(context); }
public static UserContext get() { return HOLDER.get(); }
public static Long getUserId() { UserContext ctx = get(); return ctx != null ? ctx.getUserId() : null; }
public static void clear() { HOLDER.remove(); } }
@Component public class UserContextFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { UserContext context = parseUserFromToken(request); UserContextHolder.set(context); chain.doFilter(request, response); } finally { UserContextHolder.clear(); } } }
@Service public class OrderService { public Order createOrder(OrderRequest request) { Long userId = UserContextHolder.getUserId(); } }
|
2. 数据库连接/事务
1 2 3 4 5 6 7 8
| public abstract class TransactionSynchronizationManager { private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations"); }
|
3. 日期格式化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class DateUtils { private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
private static final ThreadLocal<SimpleDateFormat> DATETIME_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static String formatDate(Date date) { return DATE_FORMAT.get().format(date); }
public static String formatDateTime(Date date) { return DATETIME_FORMAT.get().format(date); }
public static Date parseDate(String str) throws ParseException { return DATE_FORMAT.get().parse(str); } }
|
4. 链路追踪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class TraceContext { private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>(); private static final ThreadLocal<String> SPAN_ID = new ThreadLocal<>();
public static void setTraceId(String traceId) { TRACE_ID.set(traceId); }
public static String getTraceId() { return TRACE_ID.get(); }
public static void clear() { TRACE_ID.remove(); SPAN_ID.remove(); } }
|
5. 分页参数传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class PageHelper { private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<>();
public static <E> Page<E> startPage(int pageNum, int pageSize) { Page<E> page = new Page<>(pageNum, pageSize); LOCAL_PAGE.set(page); return page; }
public static <E> Page<E> getLocalPage() { return LOCAL_PAGE.get(); }
public static void clearPage() { LOCAL_PAGE.remove(); }
}
|
与synchronized的对比
| 特性 |
synchronized |
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
|
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static String format1(Date date) { synchronized (sdf) { return sdf.format(date); } }
private static final ThreadLocal<SimpleDateFormat> sdfHolder = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static String format2(Date date) { return sdfHolder.get().format(date); }
public static String format3(Date date) { return new SimpleDateFormat("yyyy-MM-dd").format(date); }
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static String format4(LocalDate date) { return formatter.format(date); }
|
最佳实践总结
DO(应该做)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private static final ThreadLocal<User> userLocal = new ThreadLocal<>();
private static final ThreadLocal<List<String>> listLocal = ThreadLocal.withInitial(ArrayList::new);
try { userLocal.set(user); doSomething(); } finally { userLocal.remove(); }
ExecutorService executor = TtlExecutors.getTtlExecutorService( Executors.newFixedThreadPool(10) );
|
DON’T(不应该做)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| userLocal.set(user); doSomething();
ThreadLocal<byte[]> dataLocal = new ThreadLocal<>(); dataLocal.set(new byte[10 * 1024 * 1024]);
public void process(User user) { }
|
总结
| 要点 |
说明 |
| 存储位置 |
数据存在Thread对象的ThreadLocalMap中 |
| key |
ThreadLocal实例本身(弱引用) |
| value |
用户设置的值(强引用) |
| 内存泄漏 |
必须手动调用remove()清理 |
| 线程池传递 |
使用TransmittableThreadLocal |
| 父子线程传递 |
使用InheritableThreadLocal |
ThreadLocal是实现线程隔离的利器,但需要注意内存泄漏问题。正确使用方式是:用完即清理,声明为static final,线程池场景使用TTL。