使Spring bean的行为类似于ExecutorService的ThreadLocal实例 [英] Making Spring beans behave like ThreadLocal instances for an ExecutorService

查看:213
本文介绍了使Spring bean的行为类似于ExecutorService的ThreadLocal实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的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);  
    }
}

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

我当时正在考虑将private Engine heavyEngine变量设为ThreadLocal变量.但是,我也是Spring的新手,所以我想知道是否可能存在另一种使用Spring注释注入ThreadLocal变量的方法.我已经考虑过将bean限定在request范围内,但是我不确定在我的设计下应该如何处理.

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

解决方案

首先放弃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将需要一个接口),该代理对象会将所有调用委派给池中的空闲对象.池大小是可配置的.当然,没有什么可以阻止您使用公共池.两种方法都保证对Engine的专有线程安全访问.

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

In my web application, I have a background service. This service uses Generator class that contains an Engine class and an ExecutorService configured to use multiple threads and that accepts 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.

I was thinking of making the private Engine heavyEngine variable 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 request scope, but I'm not sure how I should go about it given my design.

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

解决方案

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:

<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 (Engine will 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 ThreadLocalTargetSource which uses ThreadLocal instead of Commons Pool. Both approaches guarantee exclusive, thread safe access to 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.

这篇关于使Spring bean的行为类似于ExecutorService的ThreadLocal实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆