编程技巧篇之线程上下文

2022-03-25 08:33:19 浏览数 (1)

一、 背景

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

存在的问题:

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

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

二、线程上下文

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 ,很少去探索写的编码姿势。

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

0 人点赞