CDI作用域选型咨询:EngineManager多实例复用与线程安全实现
Awesome question! Let’s break down your needs first: you need EngineManager (and its underlying Engine) to be thread-safe (since Engine can’t handle concurrency), avoid the overhead of reinitializing Engine on every request, and skip the bottleneck of a singleton. Here are the best CDI approaches to solve this:
1. Use @ThreadScoped (Simple, Thread-Level Reuse)
This is the most straightforward solution if your CDI container supports it (most popular implementations like Weld or OpenWebBeans do). A @ThreadScoped bean creates exactly one instance per thread—so each thread gets its own EngineManager (and thus its own initialized Engine). When threads are reused by your app’s thread pool, the existing Engine instance is reused too, cutting down on initialization costs.
Here’s how to adjust your code:
import jakarta.enterprise.context.ThreadScoped; import jakarta.annotation.PostConstruct; @ThreadScoped public class EngineManager { private Engine engine; @PostConstruct public void init() { this.engine = // Your high-cost initialization logic here } public void doSomethingWithEngine() { // Safe to use engine here—each thread has its own instance, no concurrency issues } }
Note: @ThreadScoped is an extension to the standard CDI spec (not part of core Jakarta EE), but it’s widely supported. Just make sure your container has the extension enabled (most do by default).
2. Custom Object Pool (Fine-Grained Instance Control)
If you need to limit the total number of Engine instances (to avoid resource bloat from too many threads), use a CDI producer with an object pool (like Apache Commons Pool). This lets you reuse initialized Engine instances across threads, control maximum instance count, and ensure only one thread uses an Engine at a time.
Step 1: Set up the Engine Pool
First, create a pool factory and manager:
import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.PooledObjectFactory; import org.apache.commons.pool2.impl.DefaultPooledObject; // Factory to create/clean up Engine instances class EnginePooledFactory implements PooledObjectFactory<Engine> { @Override public PooledObject<Engine> makeObject() throws Exception { Engine engine = new Engine(); // Run your high-cost initialization here return new DefaultPooledObject<>(engine); } @Override public void destroyObject(PooledObject<Engine> pooledEngine) throws Exception { // Clean up any resources used by Engine (if needed) pooledEngine.getObject().cleanup(); } // Optional: Implement validate/activate/passivate if you need to reset instance state @Override public boolean validateObject(PooledObject<Engine> p) { return true; } @Override public void activateObject(PooledObject<Engine> p) {} @Override public void passivateObject(PooledObject<Engine> p) {} } // Pool manager to handle borrowing/returning Engine instances public class EnginePool { private final GenericObjectPool<Engine> pool; public EnginePool() { GenericObjectPoolConfig<Engine> config = new GenericObjectPoolConfig<>(); config.setMaxTotal(10); // Max number of Engine instances (adjust based on your needs) config.setMinIdle(2); // Keep 2 idle instances ready to avoid reinitialization this.pool = new GenericObjectPool<>(new EnginePooledFactory(), config); } public Engine borrowEngine() throws Exception { return pool.borrowObject(); } public void returnEngine(Engine engine) { pool.returnObject(engine); } }
Step 2: Integrate the Pool with CDI
Make the pool a singleton (@ApplicationScoped) so the whole app uses one pool, then inject it into EngineManager:
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.Dependent; import jakarta.inject.Inject; import jakarta.enterprise.inject.Produces; // Produce the singleton EnginePool @ApplicationScoped class EnginePoolProvider { @Produces public EnginePool createEnginePool() { return new EnginePool(); } } // Dependent-scoped EngineManager that uses the pool @Dependent public class EngineManager { @Inject private EnginePool enginePool; public void doSomethingWithEngine() { Engine engine = null; try { engine = enginePool.borrowEngine(); // Use engine safely—only your thread has access to this instance } catch (Exception e) { // Handle initialization/pool errors } finally { if (engine != null) { enginePool.returnEngine(engine); // Return to pool for reuse } } } }
This approach gives you full control over how many Engine instances are created, and ensures they’re only used by one thread at a time.
3. Request-Scoped with Caching (Fallback Option)
If the above options aren’t feasible, you could use @RequestScoped with a cache (like Guava Cache) to reuse Engine instances across requests handled by the same thread. However, this is a less clean solution compared to the others, since you’ll need to handle cache invalidation and thread safety manually. It’s best used as a fallback only if the first two options don’t work for your environment.
内容的提问来源于stack exchange,提问作者Flo Ryan

