异步线程本地变量丢失问题
每个用户请求进入服务,我们使用拦截器做一些前置处理,譬如查询用户的个人信息,将结果保存到线程本地变量中。在整个请求中,都能随时从堆缓存中拿到这部分信息。
相信大家也经常使用这种办法,但是某次遇到一个bug,那就是在主线程中使用异步线程去查询其他系统的信息,而异步线程是没有存这个本地变量的!结果喜闻乐见NPE。
当时是将变量作为异步线程的任务参数手动传递进去,后来看《亿级流量》,学到一种技术,可以在异步线程执行之前,将缓存注入到异步线程中,即多个线程共用一个缓存。
两个概念
线程级别本地缓存:ThreadLocal
请求级别本地缓存:HystrixRequestContext
1. 源码分析
1. HystrixRequestContext(“当铺”)
public class HystrixRequestContext implements Closeable { //这个属性类HystrixRequestContext的所有实例共享,所有线程在自己的ThreadLocalMap中存储各自的HystrixRequestContext时,都以这个作为key private static ThreadLocal<HystrixRequestContext> requestVariables = new ThreadLocal<HystrixRequestContext>(); //判断当前线程中是否有HystrixRequestContext public static boolean isCurrentThreadInitialized() { HystrixRequestContext context = requestVariables.get(); return context != null && context.state != null; } //获取当前线程中存储的HystrixRequestContext public static HystrixRequestContext getContextForCurrentThread() { HystrixRequestContext context = requestVariables.get(); if (context != null && context.state != null) { return context; } else { return null; } } //设置当前线程的HystrixRequestContext public static void setContextOnCurrentThread(HystrixRequestContext state) { requestVariables.set(state); } //初始化当前线程的HystrixRequestContext public static HystrixRequestContext initializeContext() { HystrixRequestContext state = new HystrixRequestContext(); requestVariables.set(state); return state; } //真正存储变量副本的地方 /* package */ ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>> state = new ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>>(); private HystrixRequestContext() { } //使用后需要释放防止内存泄露 public void shutdown() { if (state != null) { for (HystrixRequestVariableDefault<?> v : state.keySet()) { try { HystrixRequestVariableDefault.remove(this, v); } catch (Throwable t) { HystrixRequestVariableDefault.logger.error("Error in shutdown, will continue with shutdown of other variables", t); } } state = null; } } public void close() { shutdown(); } }
2. HystrixRequestVariableDefault(“当票”)
public class HystrixRequestVariableDefault<T> implements HystrixRequestVariable<T> { static final Logger logger = LoggerFactory.getLogger(HystrixRequestVariableDefault.class); public HystrixRequestVariableDefault() { } @SuppressWarnings("unchecked") public T get() { if (HystrixRequestContext.getContextForCurrentThread() == null) { throw new IllegalStateException(HystrixRequestContext.class.getSimpleName() + ".initializeContext() must be called at the beginning of each request before RequestVariable functionality can be used."); } //从当前线程的HystrixRequestContext中取出存储的所有变量副本 ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>> variableMap = HystrixRequestContext.getContextForCurrentThread().state; //取出对应变量的副本 HystrixRequestVariableDefault.LazyInitializer<?> v = variableMap.get(this); if (v != null) { return (T) v.get(); } HystrixRequestVariableDefault.LazyInitializer<T> l = new HystrixRequestVariableDefault.LazyInitializer<T>(this); HystrixRequestVariableDefault.LazyInitializer<?> existing = variableMap.putIfAbsent(this, l); if (existing == null) { return l.get(); } else { return (T) existing.get(); } } public T initialValue() { return null; } //设置变量副本到线程的HystrixRequestContext中 public void set(T value) { HystrixRequestContext.getContextForCurrentThread().state.put(this, new HystrixRequestVariableDefault.LazyInitializer<T>(this, value)); } //从当前线程的HystrixRequestContext中移除对应变量 public void remove() { if (HystrixRequestContext.getContextForCurrentThread() != null) { remove(HystrixRequestContext.getContextForCurrentThread(), this); } } //... }
3. HystrixContextRunnable(“中间人”)
public class HystrixContextRunnable implements Runnable { private final Callable<Void> actual; //父线程的HystrixRequestContext备份 private final HystrixRequestContext parentThreadState; public HystrixContextRunnable(Runnable actual) { this(HystrixPlugins.getInstance().getConcurrencyStrategy(), actual); } public HystrixContextRunnable(HystrixConcurrencyStrategy concurrencyStrategy, final Runnable actual) { this(concurrencyStrategy, HystrixRequestContext.getContextForCurrentThread(), actual); } public HystrixContextRunnable(final HystrixConcurrencyStrategy concurrencyStrategy, final HystrixRequestContext hystrixRequestContext, final Runnable actual) { //将任务包装成Callable this.actual = concurrencyStrategy.wrapCallable(new Callable<Void>() { @Override public Void call() throws Exception { actual.run(); return null; } }); //备份父线程的HystrixRequestContext this.parentThreadState = hystrixRequestContext; } @Override public void run() { //备份子线程的HystrixRequestContext HystrixRequestContext existingState = HystrixRequestContext.getContextForCurrentThread(); try { //用父线程的HystrixRequestContext替换给子线程,在任务执行期间,两个线程的HystrixRequestContext一致 HystrixRequestContext.setContextOnCurrentThread(parentThreadState); // try { actual.call(); } catch (Exception e) { throw new RuntimeException(e); } } finally { //还原子线程的HystrixRequestContext HystrixRequestContext.setContextOnCurrentThread(existingState); } } }
测试:
public static void main(String[] args) throws InterruptedException { //1 HystrixRequestContext context = HystrixRequestContext.initializeContext(); //2. final HystrixRequestVariableDefault<String> variableDefault = new HystrixRequestVariableDefault<>(); //3. variableDefault.set("kitty"); //4. HystrixContextRunnable runnable = new HystrixContextRunnable(() -> System.out.println(variableDefault.get())); //5. new Thread(runnable, "subThread").start(); }
第一步:
创建一个HystrixRequestContext对象H-R-C,主线程在ThreadLocalMap中存储副本,key为HystrixRequestContext的类变量requestVariables,value为H-R-C
第二步:
创建一个标记变量(暂且这么称呼吧,通过他从HystrixRequestContext的state属性中换取变量副本)H-R-V-D
第三步:
从主线程的ThreadLocalMap中拿出H-R-C,在H-R-C的state属性(一个CHM)中设置变量副本,key为H-R-V-D,value为kitty
第四步:
创建一个任务H-C-R,初始化任务时,在H-C-R中记录主线程的H-R-C
第五步:
子线程执行任务,调用H-C-R的run方法,在子线程ThreadLocalMap中存储副本,key为HystrixRequestContext的类变量requestVariables,value为父线程的H-R-C
第六步:
就是任务中定义的 System.out.println(variableDefault.get()) ,这个会从子线程的ThreadLocalMap中拿出H-R-C,然后在H-R-C的state中找H-R-V-D对应的value即kitty并输出
有点绕。。
所有的变量副本都存在HystrixRequestContext的state中,这是一个concurrentHashMap,key是HystrixRequestVariableDefault,value是变量的副本
而HystrixRequestContext这个每个线程要用的话,会在自己的ThreadLocalMap中存储,key是HystrixRequestContext的类变量requestVariables,value是线程自己创建的HystrixRequestContext
如何做到子线程共享父线程变量?
要用HystrixContextRunnable或者HystrixContextCallable,当父线程创建他两时,他们会偷偷的将父线程的HystrixRequestContext藏起来,等子线程执行任务之前,将HystrixRequestContext存到子线程的concurrentHashMap上。注意这里不是拷贝,而是传递的引用,所以父线程和子线程共享的是同一个HystrixRequestContext对象。
2. 跟ThreadLocal的区别和联系
1. 一个每个线程独享,另一个父线程和创建的所有子线程共享
2. ThreadLocal中变量副本存在Thread的ThreadLocalMap中,而HystrixRequestContext将副本存在自己的state中,却将自己存在Thread的ThreadLocalMap中,这样便于将自己整个的分享给其他线程
3. HystrixRequestContext必须使用HystrixContextRunnable或者HystrixContextCallable来创建线程
4. HystrixRequestContext的实现依赖ThreadLocal
3. 应用示例
public class RequestLocalSupport { private static final HystrixRequestVariableDefault<String> requestLocal = new HystrixRequestVariableDefault<>(); public static String getValue() { //当前线程可能没有HystrixRequestContext,所以先初始化一下 init(); //从调用线程的HystrixRequestContext中拿变量副本 return requestLocal.get(); } public static void setValue(String value) { init(); requestLocal.set(value); } //每个子线程需要在本地有HystrixRequestContext private static void init() { if (!HystrixRequestContext.isCurrentThreadInitialized()) { HystrixRequestContext.initializeContext(); } } public static void destroy() { if (HystrixRequestContext.isCurrentThreadInitialized()) { HystrixRequestContext.getContextForCurrentThread().shutdown(); } } }
public static void main(String[] args) { //假设这是切面或前置处理完成的用户信息预存 RequestLocalSupport.setValue("user info"); HystrixContextRunnable runnable1 = new HystrixContextRunnable(() -> { //任务1 用用户信息查订单 System.out.println(RequestLocalSupport.getValue()); }); HystrixContextRunnable runnable2 = new HystrixContextRunnable(() -> { //任务2 用用户信息查支付记录 System.out.println(RequestLocalSupport.getValue()); }); new Thread(runnable1).start(); new Thread(runnable2).start(); }
参考:
HystrixRequestContext实现Request级别的上下文:https://www.cnblogs.com/2YSP/p/11440700.html
源码学习:https://blog.csdn.net/myle69/article/details/85013561