关于ThreadLocal 保存信息用于存储通用信息
近期项目中由于使用ThreadLocal 造成一次生产事故,所以对ThreadLocal进行整理说明,来对ThreadLocal进行总结以备后续更好的使用。
一、 ThreadLoca 事故说明
事故说明 首先在程序中定义了静态商家List对象 List<VenderInfo> listVender ,并对期进行了数据初始化,然后第一个线程从listVender 取出一个对象放入 ThreadLocal, 当第二个线程请求进来再从listVender取出一个对象与第一个线程取出的对象相同 并对该对象进行修改,此时第一个线程中ThreadLocal存储的对象属性值已经被修改,因为修改了同一个引用类型的对象,并不是ThreadLocal 的信息被混用了。由于以上描述情况导致 第一个线程ThreadLocal存储对象的属性值被修改,再使用时出现错乱使业务数据存储StoreId信息不正确,影响业务数据。所以如果在ThreadLocal中放对象时1、放入后不要修改 2、在放入时 copy一个新对象。
1 2 3 4 | //根据商家Id,获取商家信息 VendorInfo vendorInfo = VendorContext.getVendorInfoByAreaId(areaId); vendorInfo.setRequestStoreId(storeId); vendorInfo.setRequestAreaId(areaId);<br> //把商家信息放入当前线程中VendorContext ThreadLocal 封装对象 VendorContext.setVendorInfo(vendorInfo); |
二、ThreadLocal 使用实例
概述:ThreadLocal 用于存储好多位置都使用的通用信息 一次取出后多次使用,类似于Session的使用。
1、创建ThreadLocal 存储类用于更新存储请求通用信息 其实可以说是Session
public class VendorContext { private static final ThreadLocal<VendorInfo> threadLocalInfo= new ThreadLocal<VendorInfo>(); static List<VendorInfo> listV=new ArrayList<>(); public static void beforeSetData() throws Exception { VendorInfo vendorInfo = new VendorInfo(); vendorInfo.setVendorId("123"); listV.add(vendorInfo); // 再加一个vendorInfo VendorInfo vendorInfo2 = new VendorInfo(); vendorInfo2.setVendorId("456"); listV.add(vendorInfo2); } /** * 设置threadLocal * @param areaId */ public static void setVendorInfo(String areaId){ VendorInfo vendorInfo= listV.get(0); vendorInfo.setRequestStoreId("66"+areaId); VendorInfo vendorInfo1 = new VendorInfo(); // 注意该部分一定要copy一下,用新对象,否则会出现线程安全问题,线程2修改对象vendorInfo 会影响线程1的数据 BeanUtil.copyProperties(vendorInfo, vendorInfo1); threadLocalInfo.set(vendorInfo1); } public static VendorInfo getVendorInfo(){ return threadLocalInfo.get(); } /** * 清除threadLocal */ public static void clearVendorInfo(){ threadLocalInfo.remove(); } }
2、新加拦截器 为VendorContext 中ThreadLocal对象赋值
/** * 拦截器设置 threadLocal 线程变量,用于本次会话的数据传递 * * ps:特别注意,
1 、每个线程数据被感染,如存放一个引用类型的数据, * 线程变量的清除,一定要在finally中清除,否则会导致线程变量污染
2、每次使用完以后都需要清理 VendorContext.clearVendorInfo(); 因为线程有重复使用的时候,不能感染后续线程使用。 * * */ @Component @Slf4j public class RequestInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String areaId= request.getHeader("areaId"); VendorContext.setVendorInfo(areaId); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { VendorContext.clearVendorInfo(); } }
3、 将拦截器加入springboot MVC 请求配置中
@Configuration public class MvcConfig implements WebMvcConfigurer { @Resource RequestInterceptor requestInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(requestInterceptor).addPathPatterns("/**"); } }
3、在业务模块中使用ThreadLocal 封装类VendorContext
public VendorInfo vendorInfo2; @RequestMapping("/getViewInfo") public UserInfo getView() { UserInfo userInfo= new UserInfo(); userInfo.setUserName("123"); // 获取threadLocal 数据 VendorInfo vendorInfo= VendorContext.getVendorInfo(); if(vendorInfo2==null){ vendorInfo2=vendorInfo; } if(vendorInfo==vendorInfo2){ System.out.println(" = ====" ); } // 多线中使用threadLocal,要再次为 VendorContext.setVendorInfo赋值,用完以后要清除 new Thread(new Runnable() { @Override public void run() { try { VendorContext.setVendorInfo(vendorInfo.getRequestAreaId()); VendorInfo vendorInfo= VendorContext.getVendorInfo(); System.out.println("vendorInfo.getRequestStoreId() = " + vendorInfo.getRequestStoreId()); } catch (Exception ex){ ex.printStackTrace(); } finally { VendorContext.clearVendorInfo(); } }}).start(); return userInfo; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构