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

Jen*_*ing 9 java spring multithreading

在我的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);  
    }
}
Run Code Online (Sandbox Code Playgroud)

Engine类需要很长时间才能初始化,所以理想情况下我希望每个线程只初始化一次.我不能只使它成为单例实例,因为实例不能跨多个线程共享(它依赖于顺序处理).在处理任务完成后,重用实例是完全可以的.

我想把private Engine heavyEngine变量变成ThreadLocal变量.但是,我也是Spring的新手,所以我想知道是否有其他方法可以使用Spring注释注入ThreadLocal变量.我已经考虑过将范围限定在request范围内,但我不确定在给定设计的情况下应该如何处理它.

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

Tom*_*icz 11

首先放弃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>
Run Code Online (Sandbox Code Playgroud)

现在当你注入时engine,你实际上会收到代理对象(Engine需要一个接口),它会将所有调用委托给池中的free对象.池大小是可配置的.当然,没有什么能阻止你使用ThreadLocalTargetSource哪个用户ThreadLocal代替Commons Pool.这两种方法都保证了对线程安全的独占访问Engine.

最后,您可以手动使用池(但上面解决方案的优点是它完全透明)或切换到EJB,它们按定义汇集.


hel*_*lmy 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>
Run Code Online (Sandbox Code Playgroud)

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

<bean id="myBean" class="com.foo.MyBean" scope="thread">
    ...
</bean>
Run Code Online (Sandbox Code Playgroud)