在我的网络应用程序中,我有一个后台服务。此服务使用包含Engine类和ExecutorService
的Generator类,该类配置为使用多个线程并接受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);
}
}
Engine类需要很长时间才能初始化,所以我理想的是每个线程只需要初始化一次。我不能只使它成为单例实例,因为实例不能跨多个线程共享(它依赖于顺序处理)。在处理任务完成后,重用实例是完全没问题的。
我在考虑将private Engine heavyEngine
变量设为ThreadLocal变量。但是,我也是Spring的新手,所以我想知道是否有其他方法可以使用Spring注释注入ThreadLocal变量。我已经看过将bean放到request
范围内了,但是我不知道如何根据我的设计来解决这个问题。
任何关于如何改进我的设计的指导都将受到赞赏。
答案 0 :(得分:10)
首先放弃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>
现在当你注入engine
时,你实际上会收到代理对象(Engine
将需要一个接口),它会将所有调用委托给池中的自由对象。池大小是可配置的。当然,没有什么可以阻止您使用使用ThreadLocal
代替ThreadLocalTargetSource
的Commons Pool。这两种方法都保证对Engine
的独占,线程安全访问。
最后,您可以手动使用池(但上面的解决方案的美妙之处在于它完全透明)或切换到EJB,这些EJB按定义汇总。
答案 1 :(得分:5)
仅供参考,Spring 3.0及更高版本包括一个由线程支持的Scope实现SimpleThreadScope。
要使用它,您需要注册自定义范围:
<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>
然后声明一个线程范围的bean:
<bean id="myBean" class="com.foo.MyBean" scope="thread">
...
</bean>
答案 2 :(得分:1)
我会为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();
...
}
}
可能有一种纯粹的Spring方式将引擎传递给Callable,但在这种情况下,工厂在我看来已经足够好了。