gpt4 book ai didi

java - 在 Spring 中启动多个线程时保留线程本地 bean

转载 作者:行者123 更新时间:2023-12-01 12:42:38 25 4
gpt4 key购买 nike

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

但是,一旦我尝试做一些聪明的事情(在单独的线程中启动某些任务以模仿“即发即忘”类型的调用),我注意到无论代码路径(在这个新线程中)都依赖于threadlocal bean 我被烧毁了,因为 hashMap (烘焙到上下文的类中)以前保存的详细信息现在已经消失了。对我来说,为什么它们消失了,这是完全有道理的,因为毕竟,一旦我启动了一个新线程,它就没有自动将父线程的上下文“克隆”到新子线程的上下文中。

我的问题是,是否有任何现有的 spring 框架机制(或普通的 Java)可以利用它来确保为我触发的新子线程保留这些信息?将原始父线程的上下文“深度克隆”到新子线程中有多简单?如果这是一种幼稚的方法,有人可以推荐我在开始线程之前可以参与的其他设置吗?鉴于我希望仅在请求的范围内维护此上下文,我不确定是否可以使用 something like Spring's object pooling (毕竟,我不希望出现新请求进入并为新请求回收不同/旧用户首选项的情况)。

最佳答案

我看到了三种工作方式。

使用“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;
}
}
}

如果 Thread 实例( child 变量)不是在这里创建或启动的,而是在池中重用。

使用请求/ session 范围的 bean

如果您访问数据的方式是通过某种 Bean,也许您可​​以将该 Bean 的生命周期与您的 Web 应用程序的 session 或请求联系起来。每次您都会自动获得一个新的 bean,但您会在您的范围内保留相同的 bean。

重构为缓存

Spring 有 @Cachable 注释,可以相当轻松地缓存给定的方法调用。再次,您可以将对数据的访问设置为@Cachable,并将缓存配置调整为“每个用户”。
编辑:Spring缓存使用代理,所以它是一个正在工作的方法级拦截。我称其为重构,因为它需要您做一些工作,但在我看来是一种更干净、更像 spring 的做事方式。从与上面相同的示例开始,主要区别在于您必须创建一个新的 Bean,它将实现:

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

实现只是一个细节(您可能会从当前代码中复制/粘贴它)。这个 bean 将被注入(inject)(用 Spring 的行话来说,它是一个协作者)。

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

// This is a thread pool, or whatever...
protected Object workDispatcher = null;

public void work(String userName){
Worker workUnit = new Worker(userName); // Setup
workDispatcher.dispatch(workUnit);
}

protected static class Worker implements Runnable {
// This is injected Injected in you situation (?)
protected ExpensiveUserDataCalculator userDataProvider = null;
protected String userName = null;
public Worker(String userName) { this.userName = userName; }
@Override
public void run() {
userDataProvider.computeExpensiveData(userName);
// Work ...!
return;
}
}

最后两个方法在 Spring 引用指南中有自己的部分,所以如果需要的话请检查一下。

关于java - 在 Spring 中启动多个线程时保留线程本地 bean,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24965955/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com