java 使 Spring bean 的行为类似于 ExecutorService 的 ThreadLocal 实例

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12613451/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 09:33:12  来源:igfitidea点击:

Making Spring beans behave like ThreadLocal instances for an ExecutorService

javamultithreadingspring

提问by Jensen Ching

In my web application, I have a background service. This service uses Generator class that contains an Engine class and an ExecutorServiceconfigured to use multiple threads and that accepts GeneratorTasks.

在我的 Web 应用程序中,我有一个后台服务。此服务使用 Generator 类,其中包含一个 Engine 类和一个ExecutorService配置为使用多个线程并接受 GeneratorTasks 的类。

@Component
public class Generator {
    @Autowired
    private Engine heavyEngine;

    private ExecutorService exec = Executors.newFixedThreadPool(3);

    //I actually pass the singleton instance Generator class into the task.
    public void submitTask(TaskModel model, TaskCallback callback) {
        this.exec.submit(new GeneratorTask(model, this, callback));
    }
}

@Component
public class Engine {
    public Engine() {
        //time-consuming initialization code here
    }
}

public class GeneratorTask implements Callable<String> {
    public GeneratorTask(TaskModel m, Generator g, ReceiptCallback c) {
        this.m = m;
        this.generator = g;
        this.c = c;
    }

    public String call() throws Exception {
        //This actually calls the Engine class of the generator.
        //Maybe I should have passed the Engine itself?
        this.generator.runEngine(c);  
    }
}

The Engine class takes a long time to initialize so I ideally want to initialize it only once per thread. I can't just make it a singleton instance because the instance can't be shared across multiple threads (it relies on sequential processing). It's perfectly fine to reuse the instance though, after a processing task has completed.

Engine 类需要很长时间来初始化,所以我希望每个线程只初始化一次。我不能让它成为单例实例,因为该实例不能跨多个线程共享(它依赖于顺序处理)。但是,在处理任务完成后重用实例是完全没问题的。

I was thinking of making the private Engine heavyEnginevariable a ThreadLocal variable. However, I'm also new to Spring so I was wondering if there might be another way to inject ThreadLocal variables using Spring annotations. I've looked at scoping the bean to requestscope, but I'm not sure how I should go about it given my design.

我正在考虑将private Engine heavyEngine变量设为 ThreadLocal 变量。但是,我也是 Spring 的新手,所以我想知道是否有另一种方法可以使用 Spring 注释注入 ThreadLocal 变量。我已经研究过将 bean 的request范围界定为范围,但鉴于我的设计,我不确定我应该如何去做。

Any guidance on how to improve my design would be appreciated.

任何有关如何改进我的设计的指导将不胜感激。

回答by Tomasz Nurkiewicz

First of all abandon ThreadLocal- there is something scary in that class. What you need is just object pooling. It's not well known feature, but Spring supports this as well:

首先放弃ThreadLocal- 那个班级有一些可怕的东西。您需要的只是对象池。这不是众所周知的特性,但 Spring 也支持它:

<bean id="engineProto" class="Engine" scope="prototype" lazy-init="true"/>

<bean id="engine" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource">
        <bean class="org.springframework.aop.target.CommonsPoolTargetSource">
            <property name="targetClass" value="Engine"/>
            <property name="targetBeanName" value="engineProto"/>
            <property name="maxSize" value="3"/>
            <property name="maxWait" value="5000"/>
        </bean>
    </property>
</bean>

Now when you inject engine, you'll actually receive proxy object (Enginewill need an interface) that will delegate all calls to free object in the pool. Pool size is configurable. Of course there is nothing preventing you from using ThreadLocalTargetSourcewhich uses ThreadLocalinstead of Commons Pool. Both approaches guarantee exclusive, thread safe access to Engine.

现在,当您注入时engine,您实际上会收到代理对象(Engine将需要一个接口),该对象会将所有调用委托给池中的空闲对象。池大小是可配置的。当然,没有什么可以阻止您使用ThreadLocalTargetSourcewhich usesThreadLocal而不是Commons Pool。这两种方法都保证对Engine.

Finally you can use pooling manually (but the beauty of solution above is that it's completely transparent) or switch to EJBs, which are pooled by definition.

最后,您可以手动使用池化(但上述解决方案的美妙之处在于它是完全透明的)或切换到 EJB,它们根据定义进行池化。

回答by helmy

FYI, Spring 3.0 and later includes a thread-backed Scope implementation, SimpleThreadScope.

仅供参考,Spring 3.0 及更高版本包括线程支持的 Scope 实现SimpleThreadScope

In order to use it you need to register a custom scope:

为了使用它,您需要注册一个自定义范围:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="thread">
                <bean class="org.springframework.context.support.SimpleThreadScope" />
            </entry>
        </map>
    </property>
</bean>

And then to declare a thread-scoped bean:

然后声明一个线程范围的bean:

<bean id="myBean" class="com.foo.MyBean" scope="thread">
    ...
</bean>

回答by a.b.d

I would have created a factory for Engineand call it inside GeneratorTask. By this way you can remove the heavyEnginefield inside Generatorand the Generatorconstructor argument in GeneratorTask.
Then if you want to save the initialization time of Engineyou can still declare it as a singleton but use the synchronizedkeyword on non thread safe methods.

我会创建一个工厂Engine并在里面调用它GeneratorTask。通过这种方式,您可以删除heavyEngine里面的字段Generator和中的Generator构造函数参数GeneratorTask
然后,如果您想节省初始化时间,Engine您仍然可以将其声明为单例,但synchronized在非线程安全方法上使用关键字。

public class Generator {    
    @Autowired private EngineFactory engineFactory;
    private ExecutorService exec = Executors.newFixedThreadPool(3);

    public void submitTask(TaskModel model, TaskCallback callback) {
        this.exec.submit(new GeneratorTask(engineFactory, model, callback));
    }
}

public class EngineFactory {
    @Autowired private Engine instance;

    public Engine getInstance() {
        return instance;
    }
}

public class Engine {
    public Engine() {
        //time-consuming initialization code here
    }

    public synchronized void runEngine() {
        // Do non thread safe stuf
    } 
}

public class GeneratorTask implements Callable<String> {
    public GeneratorTask(EngineFactory f, TaskModel m, ReceiptCallback c) {
        this.f = f;
        this.m = m;
        this.c = c;
    }

    public String call() throws Exception {
        Engine engine = f.getInstance();
        engine.runEngine();
        ... 
    }
}

There is probably a pure Spring way to pass the engine to the Callable but in this case the factory is good enough in my opinion.

可能有一种纯粹的 Spring 方式将引擎传递给 Callable,但在这种情况下,工厂在我看来已经足够好了。