使Spring beans像ThreadLocal实例一样在ExecutorService中运行

9

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

@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类初始化时间较长,因此我希望每个线程只初始化一次。但是我不能将其作为单例实例,因为该实例无法在多个线程之间共享(它依赖于顺序处理)。在处理任务完成后,重用该实例是完全可以的。
我考虑将私有变量heavyEngine设置为ThreadLocal变量。然而,我对Spring也不太熟悉,所以我想知道是否有另一种使用Spring注解注入ThreadLocal变量的方法。我已经尝试将bean的作用域设置为request,但是鉴于我的设计,我不确定应该如何操作。
如果能提供任何改进设计的指导,我将不胜感激。

由于 Engine 被自动装载(Autowired),我假设您已经将其声明为一个 Bean。默认情况下,Spring 的 Bean 是单例的,因此很有可能您的 Engine 已经是一个单例了。 - a.b.d
抱歉我可能没有表述清楚。我的问题是,我不能将Engine作为单例模式,因为由于其处理逻辑,它不能被多个并发线程访问。一旦完成了完整的“任务”,重用引擎是安全的,这就是我想为每个使用它的线程创建一个实例的原因。 - Jensen Ching
我明白了,我的意思是说,根据你当前的代码,Engine已经是一个单例模式了,即使这不是你想要的,但实际上你的代码已经这样做了。 - a.b.d
啊,我明白了。实际上,我的当前实现只是使用了一个singleThreadExecutor,所以我不会遇到任何并发问题。我只是在寻找其他实现类似ThreadLocal行为的方法。=) - Jensen Ching
3个回答

12

首先放弃使用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需要一个接口),该代理对象将委托所有调用到池中的自由对象。池大小可以配置。当然,您也可以使用ThreadLocalTargetSource,它使用ThreadLocal而不是Commons Pool。这两种方法都保证了对Engine的独占、线程安全访问。

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


1
类加载器内存泄漏有点可怕。 - Vedran
那么您可以将“engine” bean注入到任何期望Engine接口实例的地方?它是否只是创建了一个继承自Engine接口并在后台使用池化的匿名类? - guitar80

5

值得注意的是,Spring 3.0及其之后版本包含了一个基于线程的Scope实现,SimpleThreadScope

为了使用它,你需要注册一个自定义的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>

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

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

1

我会为Engine创建一个工厂,并在GeneratorTask中调用它。这样,您可以删除Generator中的heavyEngine字段以及GeneratorTask中的Generator构造函数参数。
然后,如果您想要节省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,但在我看来,工厂已经足够好了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接