编程技巧篇之线程上下文
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 ,很少去探索写的编码姿势。
本文提供一种使用线程上下文来提高性能和降低耦合的方式,希望对大家有帮助。
如果文章对你有帮助,欢迎点赞、关注,我会持续创作更多作品。
以上是关于编程技巧篇之线程上下文的主要内容,如果未能解决你的问题,请参考以下文章