前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编程技巧篇之线程上下文

编程技巧篇之线程上下文

作者头像
明明如月学长
发布2022-03-25 08:33:19
2400
发布2022-03-25 08:33:19
举报
文章被收录于专栏:明明如月的技术专栏

一、 背景

在实际开发过程中,有时候会采用抽象继承的方式,如模板模式、策略模式实现代码编排和复用。

存在的问题:

  • 不同流程之间会出现重复使用同一个参数调用下游接口的情况
  • 后面某个步骤依赖前面某个步骤产出的中间数据,但前面步骤的返回值中不关注这个中间数据
在这里插入图片描述
在这里插入图片描述

为了提高性能,避免重复的接口调用;为了降低耦合,可以使用线程上下文工具。

二、线程上下文

2.1 定义线程上下文

如果需要在多线程环境下使用,添加依赖

代码语言:javascript
复制
       <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>transmittable-thread-localartifactId>
            <version>2.6.1version>
        dependency>

线程上下文参考代码:

代码语言:javascript
复制
import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.HashMap;
import java.util.Map;

/**
 * 线程上下文
 */
public class ThreadContext {

    private static final ThreadLocal<Map<String, Object>> CONTEXT = new TransmittableThreadLocal<>();

    /**
     * 初始化上下文
     */
    protected static void initContext() {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {

            CONTEXT.set(new HashMap<>(8));
        } else {
            CONTEXT.get().clear();
        }
    }

    /**
     * 清除上下文
     */
    protected static void clearContext() {
        CONTEXT.remove();
    }

    /**
     * 获取上下文内容
     */
    public static <T> T getValue(String key) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            return null;
        }
        return (T) con.get(key);
    }

    /**
     * 设置上下文参数
     */
    public static void putValue(String key, Object value) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            CONTEXT.set(new HashMap<>(8));
            con = CONTEXT.get();
        }
        con.put(key, value);
    }
}

2.2 使用案例

2.2.1 复用查询结果

定义模板抽象类

代码语言:javascript
复制
public abstract class AbstractSomeService {
    
    abstract void step1(SomeReq req);
    abstract void step2(SomeReq req);
    abstract void step3(SomeReq req);

    //模板
    public final void play(SomeReq req){

      //初始化上下文
      ThreadContext.initContext();
        try{
            // 步骤1
            step1(req);

            // 步骤2
            step2(req);

            // 步骤3
            step3(req);

          }finally{
             // 清空上下文
            ThreadContext.clearContext();
        }
    }

}

定义实现类

代码语言:javascript
复制
@Component
public class ASomeServiceImpl extends AbstractSomeService {
  
  @Resource
  private UserService userService;
  
    @Override
    public void step1(SomeReq req) {
        // 查询用户信息
      UserInfo userInfo = userService.query(req.getUserId());
      ThreadContext.put("USER_INFO", userInfo)
      
         // 其他步骤
    }

    @Override
    public void step2(SomeReq req) {
        // 直接获取初始化部分已经查询过的用户信息
      UserInfo userInfo = ThreadContext.get("USER_INFO")
      
       // 其他步骤
    }

    @Override
    public void step3(SomeReq req) {
         // 代码省略
    }
}

假设 ASomeServiceImpl 的 step1 和 step2 方法中都需要查询用户信息,可以在 step1 中查询后放到线程上下文中,在 step2 中直接获取使用,避免再次执行 RPC 调用。

如果获取用户信息调用耗时 10 ms ,那么相当于降低了 10 ms ,如果还有其他查询结果可以复用,就可以降低更多耗时。

当然,有些接口可以使用这种方式,有些接口必须重新发起调用,需要根据实际情况来决定。

2.2.2 降低耦合

有时候后续环节需要前面步骤产出的一些上下文对象。

可以在该步骤中可以将该部分对象写入到上下文中,在后续执行环节从上下文中取出使用。

代码语言:javascript
复制
@Component
public class BSomeServiceImpl extends AbstractSomeService {
  
  @Resource
  private UserService userService;
  
    @Override
    public void step1(SomeReq req) {
       // 代码省略
      
        // 后续步骤需要的内容写入上下文
      SomeDTO some = xxx;
      ThreadContext.put("SOME_INFO", some)
      
        // 代码省略
    }

    @Override
    public void step2(SomeReq req) {
     
      
        // 代码省略
    }

    @Override
    public void step3(SomeReq req) {
       // 代码省略
      
         // 获取前面步骤的一些上下文
      SomeDTO some = ThreadContext.get("SOME_INFO")
        
         // 代码省略
    }
}

2.3 看法

2.3.1 殊途同归

当然线程上下文并不是唯一的解法,你也可以定义上下文对象( SomeContext)作为返回值。

将可复用的对象写入到自定义的上下文对象中,后续环节使用时直接从 SomeContext 中取出使用即可。

缺点:每个业务都需要自定义专用的上下文对象,甚至为了传递上下文需要修改函数签名(原本是 void 的返回值,会定义为 Context )。

优点:上下文中有哪些对象和对象的类型一目了然。

代码语言:javascript
复制
public abstract class AbstractSomeService {
    
    abstract SomeContext step1(SomeReq req);
  
    abstract void step2(SomeReq req, SomeContext context);
  
    abstract void step3(SomeReq req, SomeContext context);

    //模板
    public final void play(SomeReq req){
        // 步骤1
        SomeContext context = step1(req);

       // 步骤2
       step2(req, context);

       // 步骤3
       step3(req, context);
    }
}

2.3.2 可读性

使用线程上下文后,获取可复用对象和设置可复用对象的方法大概率不会放在一起。

建议使用 @see 或者 {@link } 的方式提供设置和获取方法之间的跳转快捷方式。

代码语言:javascript
复制
@Component
public class BSomeServiceImpl extends AbstractSomeService {
  
  @Resource
  private UserService userService;
  
   /**
    * 步骤1
    *
    * SOME_INFO 在 {@link BSomeServiceImpl#step3} 中使用
    */
    @Override
    public void step1(SomeReq req) {
       // 代码省略
      
        // 后续步骤需要的内容写入上下文
      SomeDTO some = xxx;
      ThreadContext.put("SOME_INFO", some)
      
        // 代码省略
    }

    @Override
    public void step2(SomeReq req) {
     
      
        // 代码省略
    }

  
  /**
  * 步骤3
  *
  * SOME_INFO 构造自 {@link BSomeServiceImpl#step1}
  */
    @Override
    public void step3(SomeReq req) {
       // 代码省略
      
         // 获取前面步骤的一些上下文
      SomeDTO some = ThreadContext.get("SOME_INFO")
        
         // 代码省略
    }
}

此外,建议大家把上下文的 key 定义为常量,这样即使不使用上面的方式跳转,也可以很快通过常量 find usage 找到设置和获取的代码。

代码语言:javascript
复制
@Component
public class BSomeServiceImpl extends AbstractSomeService {
  
  @Resource
  private UserService userService;
  
   /**
    * 步骤1
    *
    * SOME_INFO 在 {@link BSomeServiceImpl#step3} 中使用
    */
    @Override
    public void step1(SomeReq req) {
       // 代码省略
      
        // 后续步骤需要的内容写入上下文
      SomeDTO some = xxx;
      ThreadContext.put(ContextConstant.SOME_INFO, some)
      
        // 代码省略
    }

    @Override
    public void step2(SomeReq req) {
     
      
        // 代码省略
    }

  
  /**
    * 步骤3
    *
    * SOME_INFO 构造自 {@link BSomeServiceImpl#step1}
    */
    @Override
    public void step3(SomeReq req) {
       // 代码省略
      
         // 获取前面步骤的一些上下文
      SomeDTO some = ThreadContext.get(ContextConstant.SOME_INFO)
        
         // 代码省略
    }
}

三、总结

很多同学写惯了 CURD ,很少去探索写的编码姿势。

本文提供一种使用线程上下文来提高性能和降低耦合的方式,希望对大家有帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/03/20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 背景
  • 二、线程上下文
    • 2.1 定义线程上下文
      • 2.2 使用案例
        • 2.2.1 复用查询结果
        • 2.2.2 降低耦合
      • 2.3 看法
        • 2.3.1 殊途同归
        • 2.3.2 可读性
    • 三、总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档