问题

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

面试答题要点

  1. 核心场景:数据库连接管理、用户上下文传递、SimpleDateFormat线程安全、分布式追踪
  2. 适用条件:需要线程隔离 + 不适合参数传递 + 对象非线程安全
  3. Spring应用:事务管理(TransactionSynchronizationManager)、请求上下文(RequestContextHolder)
  4. 最佳实践:配合拦截器使用,统一管理生命周期,必须调用remove()
  5. 性能优化:避免锁竞争(如ThreadLocalRandom替代Random)