Making Spring beans behave like ThreadLocal instances for an ExecutorService

16,107

Solution 1

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.

Solution 2

FYI, Spring 3.0 and later includes a thread-backed Scope implementation, SimpleThreadScope.

In order to use it you need to register a custom 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>

And then to declare a thread-scoped bean:

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

Solution 3

I would have created a factory for Engine and call it inside GeneratorTask. By this way you can remove the heavyEngine field inside Generator and the Generator constructor argument in GeneratorTask.
Then if you want to save the initialization time of Engine you can still declare it as a singleton but use the synchronized keyword on non thread safe methods.

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();
        ... 
    }
}

There is probably a pure Spring way to pass the engine to the Callable but in this case the factory is good enough in my opinion.

Share:
16,107

Related videos on Youtube

Jensen Ching
Author by

Jensen Ching

Updated on September 15, 2022

Comments

  • Jensen Ching
    Jensen Ching over 1 year

    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.

  • Vedran
    Vedran almost 11 years
    Classloader memory leaks are kinda scary.
  • guitar80
    guitar80 over 7 years
    So you can inject the "engine" bean into anywhere expecting an Engine interface instance? Does it just create an anonymous class that inherits from the Engine interface that uses pooling behind-the-scenes?