近期项目中由于使用ThreadLocal 造成一次生产事故,所以对ThreadLocal进行整理说明,来对ThreadLocal进行总结以备后续更好的使用。
一、 ThreadLoca 事故说明
事故说明 首先在程序中定义了静态商家List对象 List<VenderInfo> listVender ,并对期进行了数据初始化,然后第一个线程从listVender 取出一个对象放入 ThreadLocal, 当第二个线程请求进来再从listVender取出一个对象与第一个线程取出的对象相同 并对该对象进行修改,此时第一个线程中ThreadLocal存储的对象属性值已经被修改,因为修改了同一个引用类型的对象,并不是ThreadLocal 的信息被混用了。由于以上描述情况导致 第一个线程ThreadLocal存储对象的属性值被修改,再使用时出现错乱使业务数据存储StoreId信息不正确,影响业务数据。所以如果在ThreadLocal中放对象时1、放入后不要修改 2、在放入时 copy一个新对象。
//根据商家Id,获取商家信息 VendorInfo vendorInfo = VendorContext.getVendorInfoByAreaId(areaId); vendorInfo.setRequestStoreId(storeId); vendorInfo.setRequestAreaId(areaId);
//把商家信息放入当前线程中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; }
标签:存储,vendorInfo,保存信息,VendorInfo,ThreadLocal,线程,VendorContext,public From: https://www.cnblogs.com/liyanbofly/p/18672904