编程技巧篇之线程上下文

Posted 明明如月学长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编程技巧篇之线程上下文相关的知识,希望对你有一定的参考价值。

一、 背景

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

存在的问题:

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


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


二、线程上下文

2.1 定义线程上下文

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

       <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.6.1</version>
        </dependency>

线程上下文参考代码:

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 复用查询结果

定义模板抽象类

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();
        
    



定义实现类

@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 降低耦合

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

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

@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 )。

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

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 的方式提供设置和获取方法之间的跳转快捷方式。

@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 找到设置和获取的代码。

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

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

如果文章对你有帮助,欢迎点赞、关注,我会持续创作更多作品。

以上是关于编程技巧篇之线程上下文的主要内容,如果未能解决你的问题,请参考以下文章

多线程上下文切换

并发编程之多线程篇之四

编程技巧篇之特殊处理留痕迹

Java软件开发 | 高并发编程篇之——安全访问的集合

多线程上下文切换

多线程上下文切换优化与注意