ThreadLocal的应用场景

你好,我是风一样的树懒,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。

点击下方👇关注公众号,带你一起复习后端技术,看看面试考点,补充积累技术知识,每天都为面试准备积累

文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读

现在我们开始第二部分《ThreadLocal的应用场景》,前面部分可以参考《面试经常问到的ThreadLocal的实现原理


ThreadLocal 主要用于在 多线程环境下,每个线程都需要有自己独立的数据副本,避免线程间共享变量导致的并发问题,而不需要使用 synchronized 进行同步。
适用于 同一线程内变量共享,但 线程之间相互隔离 的场景。


01
线程安全的全局变量


场景

  • 在 多线程环境 下,如果一个变量需要在 同一线程的多个方法或类中共享,但不希望被其他线程访问。

  • 例如,每个用户请求都会创建一个 requestId,在当前线程中传递,但不希望不同线程共享这个 requestId。

public class RequestContext {    private static final ThreadLocal<String> requestId = new ThreadLocal<>();    public static void setRequestId(String id) {        requestId.set(id);    }    public static String getRequestId() {        return requestId.get();    }    public static void removeRequestId() {        requestId.remove();    }}

使用

RequestContext.setRequestId("12345");System.out.println(RequestContext.getRequestId()); // 输出 12345RequestContext.removeRequestId();
  • 不同线程的 requestId 互不干扰。


02
数据库连接管理(Spring 事务管理)


场景

  • 保证同一事务(线程)内使用同一个数据库连接,防止 多次创建和关闭连接,提高性能。

  • Spring 事务管理(DataSourceTransactionManager) 就是基于 ThreadLocal 存储 数据库连接对象(Connection)。

public class ConnectionManager {    private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();    public static Connection getConnection() {        return connectionHolder.get();    }    public static void setConnection(Connection conn) {        connectionHolder.set(conn);    }    public static void removeConnection() {        connectionHolder.remove();    }}

使用

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test""user""password");ConnectionManager.setConnection(conn);Connection c = ConnectionManager.getConnection(); // 获取的是同一个连接ConnectionManager.removeConnection(); // 释放资源

优势

  • 避免在同一事务中创建多个连接,提高性能。

  • 保证数据库事务的一致性。


03
用户身份信息(登录用户)


场景

  • 在 Web 请求 处理中,用户身份信息 需要在 整个请求生命周期内传递,但 不希望被其他线程访问。

  • ThreadLocal 可以让 同一个线程内的所有代码访问当前用户,避免使用 HttpSession 共享数据。

public class UserContext {    private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();    public static void setUser(User user) {        userThreadLocal.set(user);    }    public static User getUser() {        return userThreadLocal.get();    }    public static void removeUser() {        userThreadLocal.remove();    }}

使用

// 用户登录时User user = new User("Alice");UserContext.setUser(user);// 在 Controller 或 Service 层获取用户User currentUser = UserContext.getUser();System.out.println(currentUser.getName()); // 输出 Alice// 请求结束时清理UserContext.removeUser();

优势

  • 避免显式传递 User 对象,简化代码。

  • 保证用户信息线程安全,不同线程不会访问到其他用户的数据。


04
线程池中的日志跟踪


场景

  • 分布式系统 需要 日志追踪(TraceId),保证 同一个请求的所有日志都有相同的 TraceId。

  • ThreadLocal 让 相同请求(线程)内的所有方法共享 TraceId。

示例

public class LogTraceContext {    private static final ThreadLocal<String> traceIdHolder = new ThreadLocal<>();    public static void setTraceId(String traceId) {        traceIdHolder.set(traceId);    }    public static String getTraceId() {        return traceIdHolder.get();    }    public static void removeTraceId() {        traceIdHolder.remove();    }}

使用

LogTraceContext.setTraceId(UUID.randomUUID().toString());System.out.println("TraceId: " + LogTraceContext.getTraceId()); // 记录日志LogTraceContext.removeTraceId();

优势

  • 保证日志的可追溯性,便于排查问题。

  • 不同线程的 TraceId 互不影响。



05
SimpleDateFormat 线程安全问题


场景

  • SimpleDateFormat 不是线程安全的,但 每个线程都需要一个 SimpleDateFormat 实例。

  • ThreadLocal 解决 日期格式化的线程安全问题。

示例

public class DateFormatter {    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder =        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));    public static String format(Date date) {        return dateFormatHolder.get().format(date);    }}

使用

String dateStr = DateFormatter.format(new Date());System.out.println(dateStr);

优势

  • 每个线程都有自己的 SimpleDateFormat 实例,避免线程安全问题。

  • 提高性能,避免每次创建 SimpleDateFormat 对象。



06
线程间数据传递(InheritableThreadLocal)


场景

  • ThreadLocal 不能被子线程继承,但 InheritableThreadLocal 可以。

  • 适用于 父线程创建子线程时,子线程需要继承父线程的数据。

示例

public class InheritableContext {    private static final InheritableThreadLocal<String> inheritableThreadLocal =        new InheritableThreadLocal<>();    public static void set(String value) {        inheritableThreadLocal.set(value);    }    public static String get() {        return inheritableThreadLocal.get();    }    public static void remove() {        inheritableThreadLocal.remove();    }}

使用

InheritableContext.set("Parent Thread Data");new Thread(() -> {    System.out.println("Child Thread: " + InheritableContext.get());}).start();

输出

Child Thread: Parent Thread Data

优势

  • 父线程的数据可以自动传递到子线程,适用于任务上下文传递。


07
线程隔离的对象池


场景

  • 在 高并发场景,可以为每个线程创建自己的对象池,减少对象创建开销。

示例

public class ConnectionPool {    private static final ThreadLocal<Queue<Connection>> connectionPool =        ThreadLocal.withInitial(LinkedList::new);    public static Connection getConnection() {        Queue<Connection> pool = connectionPool.get();        return pool.poll();    }    public static void returnConnection(Connection conn) {        connectionPool.get().offer(conn);    }}

优势

  • 每个线程独立管理自己的对象池,提高性能。

今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!

END


扫码关注

一起积累后端知识
不积跬步,无以至千里
不积小流,无以成江海

喜欢此内容的人还喜欢

《Java面试题指南》回归啦~


一个阿里二面面试官必问的问题


Lambda表达式说爱你不容易


分享面试:mysql数据库索引失效的情况


Spring-Boot中一个不起眼的好工具StopWatch