关于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;
    }
复制代码

 

 

  

 

  

 

 

      

posted @   xiaoBai1001  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示