Avoiding Thread Hunger Locks When Submitting Dependent Tasks to a Bounded Thread Pool

The article explains how converting a serial RPC call layer to an asynchronous parallel model can cause thread pool exhaustion and hunger lock when tasks depend on each other, demonstrates the issue with Java code, and provides practical strategies such as using separate pools or CompletableFuture to avoid the problem.

Cognitive Technology Team
Cognitive Technology Team
Cognitive Technology Team
Avoiding Thread Hunger Locks When Submitting Dependent Tasks to a Bounded Thread Pool

To improve system throughput and shorten page response time, a department refactored its aggregated service layer (the B‑side/C‑side API layer) from serial RPC calls to an asynchronous parallel mode. Shortly after deployment, the system raised numerous thread‑pool resource‑exhaustion alarms.

Exception log:

Exception in thread "main" java.util.concurrent.ExecutionException: 
java.util.concurrent.RejectedExecutionException: 
Task java.util.concurrent.FutureTask@42936575[Not completed, task = xxxxx] rejected from java.util.concurrent.ThreadPoolExecutor@33f18ac[Running, pool size = X, active threads = X, queued tasks = N, completed tasks = M]

The logs show that both the thread pool and its queue are exhausted. The root cause was not an insufficient pool size but business code that submitted tasks with mutual dependencies into the same pool.

Pitfall: Executing inter‑dependent tasks in a limited‑size thread pool leads to a hunger lock, where parent tasks occupy threads while waiting for child tasks that cannot be scheduled.

Problematic code (thread‑pool definition):

private static final ExecutorService poolExecutor = new ThreadPoolExecutor(2, 2,
            0L, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(1),
            new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(),
            new ThreadPoolExecutor.AbortPolicy() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                    log.error("rejectedExecution");
                    super.rejectedExecution(r, e);
                }
            }
    );

Pool size: 2, queue size: 1, rejection policy: AbortPolicy.

Problematic code (tasks with dependencies):

/**
 * @author 认知科技技术团队
 * 微信公众号:认知科技技术团队
 */
public static void main(String[] args) throws ExecutionException, InterruptedException {
    Future<Object> future1 = poolExecutor.submit(() -> {
        Future<Object> future = poolExecutor.submit(() -> null);
        return future.get();
    });
    Future<Object> future2 = poolExecutor.submit(() -> {
        Future<Object> future = poolExecutor.submit(() -> null);
        return future.get();
    });
    future1.get();
    future2.get();
    poolExecutor.shutdown();
}

Each parent task submits a child task to the same pool and waits for its result. Because the pool has only two threads, both threads become occupied by the parent tasks, leaving no free thread for the child tasks, which results in a hunger lock.

How to avoid the pitfall:

Do not use a small or fixed‑size thread pool for tasks that have inter‑dependencies.

Avoid using Future#get() without a timeout; it blocks indefinitely.

Consider CallerRunsPolicy as a rejection policy, but be aware it may affect web‑container threads.

Isolate dependent tasks into separate thread pools.

Use CompletableFuture with a custom thread pool to orchestrate dependent tasks.

Conclusion

Never execute inter‑dependent tasks in a bounded thread pool; doing so can cause a hunger lock and system failure. Isolate dependent tasks into different pools or employ CompletableFuture with a dedicated executor to safely compose such tasks.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencydeadlockThreadPoolAsyncExecutorService
Cognitive Technology Team
Written by

Cognitive Technology Team

Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.