保留threadlocal bean,同时在Spring中启动多个线程

时间:2014-07-25 23:31:40

标签: java multithreading spring

在传统设置中,我的应用程序执行简单的操作:接受请求,加载一些与发送该请求的特定用户相关的元数据(来自沉重的休息调用),并将此元数据存储在threadlocal bean上下文中(即一个简单的hashMap支持上下文的类)这样我就可以轻松地从代码路径中的任何其他类访问这些细节,只要我注入bean。这与在我的API生命周期中注入的所有其他非threadlocal bean协调工作,因此没有任何问题。

然而,一旦我尝试做一些聪明的事情(在一个单独的线程中启动一些任务来模仿“火灾和忘记”类型的调用)我注意到代码路径(在这个新线程中)依赖于threadlocal bean我被烧了,因为之前持有的hashMap(加入上下文的类)的细节现在已经消失了。对我来说,为什么它们已经蒸发是完全合理的,因为毕竟,一旦我开始一个新线程,它就没有自动“克隆”父线程的上下文到新子线程的上下文中。

我的问题是,是否存在任何现有的Spring框架机制(或简称Java),我可以利用它来确保为我发起的新子线程保留此信息?将原始父线程的上下文“deepClone”到新孩子中是多么的微不足道?如果这是一种天真的方式,有人可以推荐一些其他设置,我可以在开始线程之前参与吗?鉴于我正在寻求仅针对请求的范围维护此上下文,我不确定是否可以使用something like Spring's object pooling(毕竟,我不希望出现新请求和回收不同的情况/旧用户对新用户的偏好。)

1 个答案:

答案 0 :(得分:0)

我认为你可以采用三种方式。

使用'InheritableThreadLocal`

这是ThreadLocal的子类和默认的JDK功能。 可继承的线程本地文件将自动从其父级的子线程中复制。 看起来像个好人。
编辑:如果您的代码看起来像这样,那么它就是替代品。

public class Test {

// I guess this, in your actual code, is an injected Reference
InheritableThreadLocal<String> expensiveData = new InheritableThreadLocal<String>();

public void work(String userName){
    expensiveData.set(computeExpensiveData(userName)); // whatever that is

    Worker workUnit = new Worker();
    workUnit.userData = expensiveData; // Spring does some variant of this (I guess)

    Thread child = new Thread(workUnit); // This is the key line
    child.start(); // As long as the parent thread has a clean "expensiveData"
                   // The child thread will have too.
}

protected static class Worker implements Runnable {
    // This is injected too...
    protected InheritableThreadLocal<String> userData = null;
    @Override
    public void run() {
        userData.get(); // Returns spwaning thread's version
        // Work ...!
        return;
    }
}
}

如果Worker个实例(此处为userData变量)未创建,则Thread的{​​{1}}存在实际风险。从这里开始,但在游泳池中重复使用。

使用请求/会话范围的bean

如果您访问数据的方式是通过某种bean,也许您可​​以将此bean的生命周期与您的webapp会话或请求联系起来。你每次都会自动获得一个新的bean,但你会在你的范围内保持同一个bean。

重构为缓存

Spring有@Cachable注释,允许对给定方法调用进行相当容易的缓存。再一次,您可以将数据访问权限设置为@Cachable,并将缓存配置调整为“按用户” 编辑:Spring缓存使用代理,因此它是一个正在运行的metod-leve拦截。 我称这是一个因素,因为它需要你做一些工作,但在我看来 是一种更干净,更像春天的事情。从上面的相同示例开始,关键的区别在于您必须创建一个新的Bean,它将实现:

child

实现只是一个细节(您可能会从当前代码中复制/粘贴它)。 这个bean会被注入(它是Spring的术语中的协作者)。

这看起来像这样。请注意,您不必操纵ThreadLocal或生成线程或其他任何东西。让每个协作者完成它的工作,将它们与Spring连接在一起,然后使用@Cacheable进行优化(或者你认为合适的其他缓存方式)。然后缓存是工作流程的“实现细节”。

public static interface ExpensiveUserDataCalculator {
    @Cacheable // With the proper configuration tuning (eviction ? TTL ? size ?)
    public String computeExpensiveData(String userName);
}

最后两个approches在Spring Reference Guide中有自己的部分,所以如果需要,请查看它。