Spring Boot:如何确保Bean创建与销毁在同一线程执行
Hey there! I get your frustration with this third-party library's strict thread requirement—nothing worse than unexpected JVM crashes and disk space bloat from dump files. Let's fix this by making sure both your Engine initialization and deinitialization run on the exact same thread.
The Core Issue
By default, Spring runs bean initialization during container startup (usually on the main thread or a container-managed thread) and @PreDestroy methods during shutdown on a different thread. That's why your current setup violates the library's thread consistency rule.
Step-by-Step Fix
We'll use a single-threaded executor to bind both operations to the same thread. Here's how to implement it:
- Define a singleton single-threaded executor: This ensures all tasks submitted to it run sequentially on the same thread.
- Run initialization in the executor: Instead of initializing the engine directly in the
@Beanmethod, submit the initialization task to our executor and wait for the result. - Run deinitialization in the same executor: Submit the deinitialization task to the same executor during the bean's destruction phase.
Full Code Implementation
import org.springframework.beans.factory.DisposableBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @Configuration public class EngineConfig { // Single-threaded executor to enforce thread consistency @Bean(destroyMethod = "shutdown") public ExecutorService engineThreadExecutor() { return Executors.newSingleThreadExecutor(); } @Bean public IEngine ocrEngine(ExecutorService engineThreadExecutor) throws Exception { // Submit initialization task to our dedicated thread Future<IEngine> initFuture = engineThreadExecutor.submit(() -> Engine.InitializeEngine( sdkLibraryPath, projectId, sdkLicensePath, sdkLicensePassword, "", "", false ) ); // Wait for initialization to complete (adjust timeout as needed) try { return initFuture.get(10, TimeUnit.SECONDS); } catch (Exception e) { // Clean up executor if initialization fails engineThreadExecutor.shutdownNow(); throw new RuntimeException("Failed to initialize OCR engine", e); } } @Bean public EngineCleanup engineCleanup(ExecutorService engineThreadExecutor) { return new EngineCleanup(engineThreadExecutor); } // Custom cleanup bean to handle deinitialization in the same thread public static class EngineCleanup implements DisposableBean { private final ExecutorService executor; public EngineCleanup(ExecutorService executor) { this.executor = executor; } @Override public void destroy() throws Exception { // Submit deinitialization task to the same thread Future<Void> destroyFuture = executor.submit(() -> { Engine.DeinitializeEngine(); return null; }); // Wait for deinitialization to finish before shutting down executor try { destroyFuture.get(5, TimeUnit.SECONDS); } finally { executor.shutdown(); executor.awaitTermination(10, TimeUnit.SECONDS); } } } }
Key Notes
- Single-threaded Executor: The
newSingleThreadExecutor()guarantees all tasks run on the same thread, so initialization and deinitialization are thread-consistent. - Timeout Handling: Added timeouts to
get()calls to prevent your application from hanging if the library gets stuck during init/destroy. - Cleanup Order: We wait for deinitialization to complete before shutting down the executor, ensuring the library's cleanup logic finishes properly.
- Error Handling: If initialization fails, we shut down the executor immediately to avoid leaving idle threads.
This setup will ensure your Engine is initialized and deinitialized on the exact same thread, eliminating the JVM crash risk and the resulting dump file bloat.
内容的提问来源于stack exchange,提问作者stirante

