ThreadLocal的优点:
- 线程隔离:每个线程都有自己独立的变量副本,不会受到其他线程的影响,可以避免线程安全问题。
- 高效性:由于每个线程都有自己的变量副本,不需要进行额外的同步操作,可以提高程序的执行效率。
- 简单易用:使用 ThreadLocal 可以方便地在多线程环境下管理登录信息,不需要手动进行线程间的变量传递。
缺点
- 内存泄漏:如果没有及时清理 ThreadLocal 中的变量副本,可能会导致内存泄漏问题。因为 ThreadLocal 中的变量副本是与线程绑定的,如果线程一直存在,那么对应的变量副本也会一直存在,可能会占用大量的内存空间。
- 上下文切换问题:由于每个线程都有自己的变量副本,当需要在多个线程之间共享数据时,可能需要进行额外的上下文切换操作,增加了程序的复杂性和开销。
为什么要封装ThreadLocal?
原因有两点:
1、对于Thread,如果希望在Interceptor中存入User并在Service层通过ThreadLocal把User取出来,必须保证Interceptor和Service此时用的是同一个ThreadLocal。
2.一个对象如何同时出现在Interceptor和Service呢?各自new一个ThreadLocal可不行,因为此时是两个对象了。
所以我们定义一个类
简单版本(小白使用)
这里定义了一个threadlocal的工具类,这是最简单的版本
/**
* @author ljc
*/
public class MyThreadLocal {
private MyThreadLocal() {
}
private static final ThreadLocal<Object> THREAD_CONTEXT = new ThreadLocal<>();
public static void set(Object obj) {
THREAD_CONTEXT.set(obj);
}
public static Object get() {
return THREAD_CONTEXT.get();
}
public static void remove() {
THREAD_CONTEXT.remove();
}
}
但是简单版本存在一个问题就是只能存单个对象,如果存多个对象会覆盖掉前面的对象
高级版本(推荐):
package com.heima.utils.thread;
import java.util.HashMap;
import java.util.Map;
/**
* @author ljc
* 参考 mx
*/
public class ThreadLocalUtil {
private ThreadLocalUtil() {
}
/**
* 将ThreadLocal泛型指定为String,那么造了一个ThreadLocalMap后,这个map只能存 threadLocal:"这是字符串" 这样的键值对
* 将ThreadLocal泛型指定为Integer,那么造了一个ThreadLocalMap后,这个map只能存 threadLocal:1111111111 这样的键值对
* 由于单纯的value会发生值覆盖,所以我们使用Map<String, Object>作为value
*/
private static final ThreadLocal<Map<String, Object>> THREAD_CONTEXT = new ThreadLocal<>();
/**
* 存入线程变量
* @param key
* @param object
*/
public static void put(String key, Object object) {
/**
* ThreadLocalMap的构造类似于这样
* {
* ...THREAD_CONTEXT: {
* ........."USER_INFO":"{'name':'bravo', 'age':18}",
* ........."SCORE":"{'Math':99, 'English': 97}"
* ......}
* }
*
* 2.ThreadLocalMap.Entry e = map.getEntry(this); 把自己(THREAD_CONTEXT)作为key,取出属于自己的value,此时value是一个Map<String, Object>。
* 3.所以最终THREAD_CONTEXT.get()返回的Map<String, Object> map
*
*/
Map<String, Object> map = THREAD_CONTEXT.get();
// 第一次从ThreadLocalMap中根据threadLocal取出的value可能是null
if (map == null) {
map = new HashMap<>();
// 把map作为value放进去
THREAD_CONTEXT.set(map);
}
/**
* 假设本次存的是 USER_INFO:{"name":"bravo", "age":18}
* 此时ThreadLocalMap中的结构是
* {
* ...THREAD_CONTEXT: {
* ........."USER_INFO":"{'name':'bravo', 'age':18}",
* ......}
* }
*
*/
map.put(key, object);
}
/**
* 取出线程变量
*
* @param key
* @return
*/
public static Object get(String key) {
// 先获取Map
Map<String, Object> map = THREAD_CONTEXT.get();
// 从Map中得到USER_INFO
return map != null ? map.get(key) : null;
}
/**
* 移除当前线程的指定变量
* 比如把
* {
* ...THREAD_CONTEXT: {
* ........."USER_INFO":"{'name':'bravo', 'age':18}",
* ........."SCORE":"{'Math':99, 'English': 97}"
* ......}
* }
* 变成
* {
* ...THREAD_CONTEXT: {
* ........."SCORE":"{'Math':99, 'English': 97}"
* ......}
* }
* 并不是移除所有,而是只移除USER_INFO
* @param key
*/
public static void remove(String key) {
Map<String, Object> map = THREAD_CONTEXT.get();
map.remove(key);
}
/**
* 移除当前线程的所有变量
* 比如把
* {
* ...THREAD_CONTEXT: {
* ........."USER_INFO":"{'name':'bravo', 'age':18}",
* ........."SCORE":"{'Math':99, 'English': 97}"
* ......}
* }
* 变成
* {
* }
*/
public static void clear() {
THREAD_CONTEXT.remove();
}
}
拦截器中拦截数据放到threadlocal(拦截器在相应的微服务中设置)
// 自定义拦截器 实现HandlerInterceptor 并复写里面的方法
public class AppTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头中取出数据放到
String userId = request.getHeader("userId");
if (userId != null){
ApUser user = new ApUser();
user.setId(Integer.valueOf(userId));
ThreadLocalUtil.put("user",user);
}
return true;
}
// controller 出错 该方法就不执行了
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
ThreadLocalUtil.clear();
}
// controller方法出错,依然执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ThreadLocalUtil.clear();
}
}
然后添加拦截器的配置类让其生效,并设置拦截范围
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AppTokenInterceptor()).addPathPatterns("/**");
}
}