异步线程本地变量丢失问题

每个用户请求进入服务,我们使用拦截器做一些前置处理,譬如查询用户的个人信息,将结果保存到线程本地变量中。在整个请求中,都能随时从堆缓存中拿到这部分信息。

相信大家也经常使用这种办法,但是某次遇到一个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

人生就像蒲公英,看似自由,其实身不由己。
原文地址:https://www.cnblogs.com/walker993/p/14706623.html