问题
ThreadLocal的应用场景有哪些?
答案
核心适用场景
ThreadLocal适用于需要线程隔离且不适合通过参数传递的场景。典型特征:
- 对象在同一线程内多个方法间共享
- 对象非线程安全(如SimpleDateFormat)
- 希望避免加锁的性能损耗
- 不希望污染方法签名(不传递额外参数)
典型应用场景
1. 数据库连接管理(事务一致性)
问题:同一次请求中的多个DAO操作需要使用同一个Connection,以保证事务一致性。
解决方案:使用ThreadLocal存储Connection,避免通过参数层层传递。
public class ConnectionManager {
// 使用ThreadLocal存储当前线程的数据库连接
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
// 获取当前线程的连接
public static Connection getConnection() throws SQLException {
Connection conn = connectionHolder.get();
if (conn == null || conn.isClosed()) {
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
connectionHolder.set(conn);
}
return conn;
}
// 关闭连接并清理ThreadLocal
public static void closeConnection() throws SQLException {
Connection conn = connectionHolder.get();
if (conn != null) {
conn.close();
connectionHolder.remove(); // 必须清理!
}
}
}
// Service层使用示例
public class UserService {
public void transferMoney(int fromId, int toId, int amount) throws SQLException {
try {
Connection conn = ConnectionManager.getConnection();
conn.setAutoCommit(false); // 开启事务
// 多个DAO操作共享同一个Connection
accountDao.deduct(fromId, amount); // 内部调用getConnection()
accountDao.add(toId, amount); // 获取到同一个连接
conn.commit(); // 提交事务
} catch (Exception e) {
ConnectionManager.getConnection().rollback();
throw e;
} finally {
ConnectionManager.closeConnection(); // 清理
}
}
}
优势:
- DAO层无需传递Connection参数,代码简洁
- 保证同一线程内的所有数据库操作使用同一连接
- Spring的
@Transactional底层也使用类似机制
2. 用户上下文传递(Web请求)
问题:在Controller、Service、DAO等多层调用中需要访问当前登录用户信息,传递参数繁琐。
解决方案:使用ThreadLocal存储用户上下文,配合拦截器自动设置和清理。
// 用户上下文持有类
public class UserContext {
private static ThreadLocal<User> currentUser = new ThreadLocal<>();
public static void setCurrentUser(User user) {
currentUser.set(user);
}
public static User getCurrentUser() {
return currentUser.get();
}
public static Long getCurrentUserId() {
User user = currentUser.get();
return user != null ? user.getId() : null;
}
public static void clear() {
currentUser.remove();
}
}
// Spring拦截器
@Component
public class UserContextInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, ...) {
// 从请求头或Session中获取用户信息
String token = request.getHeader("Authorization");
User user = authService.getUserByToken(token);
UserContext.setCurrentUser(user); // 设置到ThreadLocal
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, ...) {
UserContext.clear(); // 请求结束后清理
}
}
// Service层使用
@Service
public class OrderService {
public void createOrder(OrderDTO orderDTO) {
// 无需传递userId参数,直接从上下文获取
Long userId = UserContext.getCurrentUserId();
Order order = new Order();
order.setUserId(userId);
order.setItems(orderDTO.getItems());
orderDao.save(order);
// 记录日志
logService.log("用户" + userId + "创建订单"); // 同样无需传递userId
}
}
优势:
- 避免在所有方法签名中添加User参数
- 代码更简洁,职责清晰
- 统一在拦截器中管理用户上下文生命周期
3. SimpleDateFormat线程安全问题
问题:SimpleDateFormat非线程安全,多线程共享会导致日期解析错误。
解决方案:使用ThreadLocal为每个线程创建独立的SimpleDateFormat实例。
// ❌ 错误示例:共享SimpleDateFormat导致线程安全问题
public class DateUtil {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static String format(Date date) {
return sdf.format(date); // 多线程并发调用会出错!
}
}
// ✅ 正确示例:使用ThreadLocal隔离
public class DateUtil {
private static final ThreadLocal<SimpleDateFormat> dateFormatHolder =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static String format(Date date) {
return dateFormatHolder.get().format(date);
}
public static Date parse(String dateStr) throws ParseException {
return dateFormatHolder.get().parse(dateStr);
}
// 注意:如果DateUtil在Web环境使用,需要在拦截器中清理
public static void clear() {
dateFormatHolder.remove();
}
}
// 使用示例
String dateStr = DateUtil.format(new Date()); // 线程安全
// 更推荐的做法:JDK8使用DateTimeFormatter(天然线程安全)
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String dateStr = FORMATTER.format(LocalDate.now()); // 无需ThreadLocal
4. 分布式追踪(TraceId)
问题:微服务架构下需要在日志中记录全链路TraceId,便于问题排查。
解决方案:使用ThreadLocal传递TraceId,无需修改每个方法签名。
// TraceId上下文
public class TraceContext {
private static ThreadLocal<String> traceIdHolder = new ThreadLocal<>();
public static void setTraceId(String traceId) {
traceIdHolder.set(traceId);
}
public static String getTraceId() {
return traceIdHolder.get();
}
public static void clear() {
traceIdHolder.remove();
}
}
// 过滤器
@Component
public class TraceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
// 从请求头获取或生成TraceId
String traceId = ((HttpServletRequest) request).getHeader("X-Trace-Id");
if (traceId == null) {
traceId = UUID.randomUUID().toString();
}
TraceContext.setTraceId(traceId);
// 传递给下游服务(RestTemplate拦截器中添加请求头)
chain.doFilter(request, response);
} finally {
TraceContext.clear();
}
}
}
// 日志工具类
public class LogUtil {
private static final Logger logger = LoggerFactory.getLogger(LogUtil.class);
public static void info(String message) {
String traceId = TraceContext.getTraceId();
logger.info("[TraceId:{}] {}", traceId, message);
}
}
// Service层使用
public void processOrder() {
LogUtil.info("开始处理订单"); // 自动携带TraceId
// ...
LogUtil.info("订单处理完成");
}
5. 随机数生成器(性能优化)
问题:Random类在多线程下存在CAS竞争,性能较差。
解决方案:使用ThreadLocalRandom为每个线程提供独立的随机数生成器。
// ❌ 性能较差的方式
Random random = new Random();
int value = random.nextInt(100); // 多线程竞争seed
// ✅ 推荐方式(JDK7+)
int value = ThreadLocalRandom.current().nextInt(100); // 无竞争
// ThreadLocalRandom底层使用ThreadLocal存储种子
public class ThreadLocalRandom extends Random {
// 每个线程独立的种子存储在Thread对象的threadLocalRandomSeed字段
static final void localInit() {
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p == 0) ? 1 : p;
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
}
6. 性能分析和监控
问题:需要统计每个请求的执行时间、SQL执行次数等指标。
解决方案:使用ThreadLocal存储统计信息。
public class PerformanceMonitor {
private static ThreadLocal<PerformanceData> dataHolder = new ThreadLocal<>();
public static void start() {
PerformanceData data = new PerformanceData();
data.setStartTime(System.currentTimeMillis());
dataHolder.set(data);
}
public static void recordSql() {
PerformanceData data = dataHolder.get();
if (data != null) {
data.incrementSqlCount();
}
}
public static void end() {
PerformanceData data = dataHolder.get();
if (data != null) {
long duration = System.currentTimeMillis() - data.getStartTime();
logger.info("请求耗时:{}ms, SQL执行次数:{}", duration, data.getSqlCount());
dataHolder.remove();
}
}
}
// 使用
@Component
public class MonitorInterceptor implements HandlerInterceptor {
public boolean preHandle(...) {
PerformanceMonitor.start();
return true;
}
public void afterCompletion(...) {
PerformanceMonitor.end();
}
}
Spring框架中的ThreadLocal应用
| 场景 | 类 | 作用 |
|---|---|---|
| 事务管理 | TransactionSynchronizationManager | 存储当前事务的Connection、事务状态等 |
| 请求上下文 | RequestContextHolder | 存储HttpServletRequest、HttpServletResponse |
| 国际化 | LocaleContextHolder | 存储当前请求的Locale |
| 安全上下文 | SecurityContextHolder(Spring Security) | 存储认证信息 |
// Spring事务管理器核心代码(简化版)
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
public static void bindResource(Object key, Object value) {
Map<Object, Object> map = resources.get();
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
map.put(key, value); // 存储DataSource -> Connection映射
}
public static Object getResource(Object key) {
Map<Object, Object> map = resources.get();
return (map != null ? map.get(key) : null);
}
}
使用注意事项
| 注意点 | 说明 |
|---|---|
| 必须清理 | 在finally或拦截器中调用remove(),避免内存泄漏 |
| 线程池场景 | 线程复用会导致ThreadLocal值残留,必须清理 |
| 避免大对象 | ThreadLocal适合存储轻量级对象(用户ID、上下文) |
| 使用static | 通常声明为static,避免每个实例都创建ThreadLocal |
| 考虑InheritableThreadLocal | 如果需要父子线程共享,使用InheritableThreadLocal |
面试答题要点
- 核心场景:数据库连接管理、用户上下文传递、SimpleDateFormat线程安全、分布式追踪
- 适用条件:需要线程隔离 + 不适合参数传递 + 对象非线程安全
- Spring应用:事务管理(TransactionSynchronizationManager)、请求上下文(RequestContextHolder)
- 最佳实践:配合拦截器使用,统一管理生命周期,必须调用remove()
- 性能优化:避免锁竞争(如ThreadLocalRandom替代Random)