ThreadLocal用于传递参数及优势

       Threadlocal叫做线程本地变量,也叫线程本地存储,其作用是将参数储存在线程中,然后在该线程运行的任何阶段都能从线程中获取,简单的说,就是起到一个参数传递的作用。

       举个具体运用的例子:一般用户在登陆系统之后,我们都会把一个用户的标识信息作为参数放入cookie、或session、或token中,返回给浏览器,然后在以后的请求中,就会带上这个标识信息。当在后续的请求中,我们需要用到这个标识信息时,我们可以从cookie、session、token中获取,并放入当前线程的Threadlocal中,最后就可以在Threadlocal中获取。优点是减少方法的入参个数。

(1)获取用户数据,并放入cookie中,同时返回result返回token信息(有可能浏览器屏蔽cookie),token就是对用户信息加密后数据。

    @PostMapping(value = "login")
    @ApiOperation(value = "登录", nickname = "login")
    public BaseResult<LoginResponse> login(@ApiParam @Valid @RequestBody LoginRequest request,
                                           HttpServletResponse httpServletResponse) {
        try {
            BaseResult<LoginResponse> result = loginService.login(request);
            /**
             * 登录完成回写登录token到cookie
             */
            if (result.isSuccess()) {
                Cookie cookie = CookieUtil
                        .createCookie(CommonConstant.KUC_TOKEN_COOKIE_KEY, result.getContent().getToken(),
                                maxAge, cookieDomain, true);
                httpServletResponse.addCookie(cookie);
            }
            return result;
        } catch (Exception e) {
            log.error("用户{}登录失败", request.getUserId(), e);
            throw e;
        }
    }

(2)拦截器:将HttpServletRequest中的token信息取出,并解码,然后存入到当前线程的ThreadlocalMap中。注意销毁


public class UserContextInterceptor implements HandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(UserContextInterceptor.class);


    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {
        //前端会将获取的token放到header中
        String token = httpServletRequest.getHeader("HIC_USER_CONTEXT");
        if (token != null) {
            try {
                //将token解码后的数据存入当前线程的ThreadLocalMap中
                UserContext.set(UserContext.getDecoder().decode(token));
            } catch (Exception var6) {
                logger.error("解析user context base64字符串失败,源字符串为:{}", token , var6);
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        UserContext.destroy();
    }
}

(3)工具类:作用是把信息从当前线程的ThreadlocalMap中取出或存入数据信息。


public class UserContext {
  
    public static final Encoder ENCODER = new Encoder();
    public static final Decoder DECODER = new Decoder();
    private static ThreadLocal<LoginUser> userThreadLocal = new ThreadLocal();

    public UserContext() {
    }

    public static LoginUser get() {
        return (LoginUser)userThreadLocal.get();
    }

    public static void set(LoginUser user) {
        userThreadLocal.set(user);
    }

    public static void destroy() {
        userThreadLocal.remove();
    }

    public static Encoder getEncoder() {
        return ENCODER;
    }

    public static Decoder getDecoder() {
        return DECODER;
    }
}

(4)当我们需要用到ThreadlocalMap中的数据时,我们可以在service层的实现类中直接获取。

@Service
public class XxxServiceImpl implements XxxService {

    @Override
    public BaseResult xxx() {
        LoginUser loginUser = UserContext.get();
        ……  ……  ……
    }
}

总结:Threadlocal就是起到一个传递参数的作用,它存在于线程的生命周期内。相对通过方法传递参数,它显得更隐蔽,同时也能减少方法的参数个数。

<think>好的,我现在要帮用户解答关于ThreadLocal的使用场景和实际场景的问题。用户之前问过关于Java缓存穿透的问题,现在转向了ThreadLocal,看起来他对Java并发编程感兴趣,或者是在准备面试。我需要先回忆一下ThreadLocal的基本概念,然后分析它的使用场景,再结合实际例子来说明。 首先,ThreadLocalJava中的一个类,用于在多线程环境中为每个线程提供独立的变量副本,避免线程间的数据竞争。每个线程可以通过ThreadLocal访问自己独立的变量,不会影响到其他线程。这个机制的关键在于数据的线程隔离。 接下来,我需要确定用户的需求。用户可能希望了解ThreadLocal在哪些情况下会被使用,以及实际开发中的具体应用场景。考虑到用户之前的问题涉及面试题,可能他正在准备Java相关的面试,因此需要提供清晰、结构化的回答,包含常见的应用场景和实际例子。 常见的ThreadLocal使用场景包括: 1. **线程安全的对象存储**:例如,SimpleDateFormat不是线程安全的,每个线程使用独立的实例可以避免同步问题。 2. **数据库连接和事务管理**:在框架中,如Spring,可能将数据库连接绑定到当前线程,确保事务的一致性。 3. **用户会话信息**:Web应用中,每个请求对应一个线程,用户信息可以存储在ThreadLocal中,方便随时获取。 4. **参数隐式传递**:在调用链中传递参数,避免方法参数层层传递。 实际场景的例子需要具体说明,比如在Spring的事务管理、日志框架中的上下文传递、或者Web应用中的用户身份存储。此外,需要注意ThreadLocal可能带来的内存泄漏问题,特别是在使用线程池时,线程复用导致ThreadLocal未被清理,从而积累数据。 用户可能还想知道如何正确使用ThreadLocal,避免常见陷阱,比如及时调用remove()方法。因此,在回答中应该包含最佳实践和注意事项。 总结回答结构:先介绍ThreadLocal的作用,然后分点列出使用场景,每个场景给出实际例子,最后提到注意事项。这样结构清晰,符合用户之前的问题格式,帮助用户逐步理解。</think>以下是ThreadLocal的详细使用场景及实际应用解析,按典型应用领域分层说明: --- ### 一、核心作用原理 `ThreadLocal`通过为每个线程创建变量副本实现数据隔离: ```java ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "default"); // 线程A设置值 new Thread(() -> { threadLocal.set("A的值"); System.out.println(threadLocal.get()); // 输出"A的值" }).start(); // 线程B获取值 new Thread(() -> { System.out.println(threadLocal.get()); // 输出"default" }).start(); ``` --- ### 二、典型使用场景 #### 1. 线程不安全工具类隔离 **场景**:解决`SimpleDateFormat`等非线程安全类的并发问题 **实现方案**: ```java private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public String formatDate(Date date) { return dateFormatHolder.get().format(date); // 每个线程独立实例 } ``` **优势**:避免`synchronized`锁竞争,提升并发性能 --- #### 2. 数据库连接管理(ORM框架核心) **场景**:MyBatis的`SqlSession`管理 **源码级实现**: ```java public class SqlSessionManager { private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>(); public void startManagedSession() { localSqlSession.set(openSession()); } public SqlSession getSession() { return localSqlSession.get(); } } ``` **效果**:保证同一事务中多个DAO操作使用同一个连接 --- #### 3. 请求上下文传递(Web应用) **场景**:Spring MVC中存储当前用户信息 **实战代码**: ```java public class UserContext { private static final ThreadLocal<User> currentUser = new ThreadLocal<>(); public static void setUser(User user) { currentUser.set(user); } public static User getUser() { return currentUser.get(); } // Filter中清除 public static void clear() { currentUser.remove(); } } ``` **调用链示例**: ``` HTTP请求 → 拦截器解析token → UserContext.setUser() → Service层 → UserContext.getUser() → 清除ThreadLocal ``` --- #### 4. 跨层级参数传递(日志追踪) **场景**:全链路请求ID透传 **Slf4j MDC实现**: ```java public class TraceFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { MDC.put("traceId", UUID.randomUUID().toString()); // 基于ThreadLocal try { chain.doFilter(req, res); } finally { MDC.clear(); } } } ``` **日志输出**:`%X{traceId}`自动附加到每行日志 --- ### 三、实际应用陷阱 #### 1. 线程池污染问题 **异常场景**: ```java ExecutorService pool = Executors.newFixedThreadPool(5); pool.execute(() -> { threadLocal.set("重要数据"); // 未执行remove() }); // 该线程被复用后仍保留旧数据 ``` **解决方案**: ```java try { threadLocal.set(data); // ...业务逻辑 } finally { threadLocal.remove(); // 必须强制清理 } ``` #### 2. 内存泄漏根源 **强引用链**: `Thread → ThreadLocalMap → Entry → value` **优化建议**: - 使用`static final`修饰ThreadLocal实例(避免Entry.key内存泄漏) - 显式调用remove()方法 --- ### 四、新型替代方案 | 场景 | 传统方案 | Java17+改进方案 | |--------------------|-----------------|--------------------| | 上下文传递 | ThreadLocal | Scoped Values | | 不可变数据共享 | InheritableThreadLocal | Virtual Threads | --- **选型建议**:优先使用框架内置方案(如Spring的`RequestContextHolder`),自行创建ThreadLocal时应严格遵循`try-finally`清理规范。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值