家居装饰
28.14M · 2026-04-12
在多线程编程中,ThreadLocal 是一个常被用到却又容易踩坑的工具类。它能让每个线程拥有自己专属的变量副本,实现线程间的数据隔离,但如果使用不当,就可能引发内存泄漏等严重问题。
ThreadLocal 提供了线程本地变量,每个访问该变量的线程都有自己独立初始化的变量副本,线程之间互不干扰。常见应用场景包括:
ThreadLocal 的实现涉及三个核心组件:Thread、ThreadLocal、ThreadLocalMap,它们的关系如下:
Thread 对象内部都维护一个 ThreadLocalMap 类型的成员变量 threadLocalsThreadLocalMap 是 ThreadLocal 的静态内部类,内部使用 Entry 数组存储数据Entry 继承自 WeakReference<ThreadLocal<?>>,其中 key 是 ThreadLocal 对象(弱引用),value 是线程的变量副本(强引用)set 方法用于设置当前线程的线程本地变量值:
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 方法用于获取当前线程的线程本地变量值:
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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
protected T initialValue() {
return null;
}
remove 方法用于移除当前线程的线程本地变量:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
ThreadLocalMap 内部使用 Entry 数组存储数据,初始容量为 16,负载因子为 2/3,扩容时容量翻倍。与 HashMap 使用链地址法解决哈希冲突不同,ThreadLocalMap 使用线性探测法(开放寻址法)解决哈希冲突。
Entry 的 key 是 WeakReference<ThreadLocal<?>>(弱引用),value 是强引用:
当 ThreadLocal 对象失去外部强引用时,发生 GC 会导致 key(ThreadLocal)被回收变为 null,但 value 是强引用,只要线程还活着(如线程池中的核心线程),ThreadLocalMap 就会存在,Entry 也会存在,value 无法被回收,从而导致内存泄漏。
如果 key 是强引用,即使 ThreadLocal 对象失去外部强引用,key 仍会指向 ThreadLocal,导致 ThreadLocal 无法被回收,内存泄漏会更严重。使用弱引用是为了尽量降低内存泄漏风险,但 value 的强引用问题仍需通过主动调用 remove 解决。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.49</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.5</version>
</dependency>
</dependencies>
package com.jam.demo.context;
import lombok.Data;
/**
* 用户上下文
* @author ken
*/
@Data
public class UserContext {
private Long userId;
private String username;
}
package com.jam.demo.util;
import com.jam.demo.context.UserContext;
import org.springframework.util.ObjectUtils;
/**
* ThreadLocal工具类
* @author ken
*/
public class UserContextHolder {
private static final ThreadLocal<UserContext> USER_CONTEXT_HOLDER = new com.alibaba.ttl.TransmittableThreadLocal<>();
private UserContextHolder() {
}
/**
* 设置用户上下文
* @param userContext 用户上下文
*/
public static void set(UserContext userContext) {
if (!ObjectUtils.isEmpty(userContext)) {
USER_CONTEXT_HOLDER.set(userContext);
}
}
/**
* 获取用户上下文
* @return 用户上下文
*/
public static UserContext get() {
return USER_CONTEXT_HOLDER.get();
}
/**
* 清理用户上下文
*/
public static void remove() {
USER_CONTEXT_HOLDER.remove();
}
}
package com.jam.demo.interceptor;
import com.jam.demo.context.UserContext;
import com.jam.demo.util.UserContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 用户上下文拦截器
* @author ken
*/
@Slf4j
@Component
public class UserContextInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String userId = request.getHeader("userId");
String username = request.getHeader("username");
if (org.springframework.util.StringUtils.hasText(userId) && org.springframework.util.StringUtils.hasText(username)) {
UserContext userContext = new UserContext();
userContext.setUserId(Long.parseLong(userId));
userContext.setUsername(username);
UserContextHolder.set(userContext);
log.info("用户上下文设置成功:{}", userContext);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
UserContextHolder.remove();
log.info("用户上下文已清理");
}
}
package com.jam.demo.config;
import com.jam.demo.interceptor.UserContextInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置
* @author ken
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final UserContextInterceptor userContextInterceptor;
public WebMvcConfig(UserContextInterceptor userContextInterceptor) {
this.userContextInterceptor = userContextInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userContextInterceptor)
.addPathPatterns("/**");
}
}
package com.jam.demo.controller;
import com.jam.demo.context.UserContext;
import com.jam.demo.util.UserContextHolder;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户信息Controller
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "用户信息管理", description = "用户信息相关接口")
public class UserController {
@GetMapping("/info")
@Operation(summary = "获取当前用户信息")
public UserContext getUserInfo() {
UserContext userContext = UserContextHolder.get();
log.info("获取当前用户信息:{}", userContext);
return userContext;
}
}
package com.jam.demo.badcase;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* ThreadLocal错误使用示例
* @author ken
*/
@Slf4j
public class ThreadLocalMemoryLeakDemo {
private static final ThreadLocal<byte[]> THREAD_LOCAL = new ThreadLocal<>();
private static final int BUFFER_SIZE = 1024 * 1024 * 10;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
THREAD_LOCAL.set(new byte[BUFFER_SIZE]);
log.info("设置数据:{}", Thread.currentThread().getName());
});
}
executorService.shutdown();
}
}
ThreadLocal 通过线程本地变量实现了线程间的数据隔离,其底层依赖 Thread 内部的 ThreadLocalMap 存储数据,使用线性探测法解决哈希冲突。内存泄漏的根本原因是 Entry 的 value 为强引用,当线程长期存活时,key 为 null 的 Entry 无法被自动清理。生产环境使用 ThreadLocal 的核心是每次使用完必须调用 remove 方法,配合 try-finally 块确保清理执行,线程池场景推荐使用 TransmittableThreadLocal 解决上下文传递问题。