9

在我的 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范围,但我不确定鉴于我的设计我应该如何去做。

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

4

3 回答 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将需要一个接口),它将所有调用委托给池中的空闲对象。池大小是可配置的。当然,没有什么可以阻止您使用ThreadLocalTargetSourcewhich usesThreadLocal而不是Commons Pool。这两种方法都保证了对Engine.

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

于 2012-09-27T15:58:11.740 回答
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>
于 2014-11-12T21:13:52.983 回答
1

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

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,但在这种情况下,我认为工厂已经足够好了。

于 2012-09-27T04:08:17.737 回答