Deadlock and thread-pool starvation visualizer
Step two threads through two locks and watch a deadlock form, then fix it with lock ordering or tryLock. Switch to the pool tab to see a fixed thread pool starve itself with nested submit.
Explain what I am seeing
What just happened
Explain what I am seeing
The Java behind it
Red = outer task, green = inner task, dark red = a thread blocked waiting for an inner task.
Two ways to hang a Java application
A lock-ordering deadlock needs nothing more than two locks and two code paths. One path takes lock A and then lock B (for example, debiting account A and then crediting account B). Another path does the reverse. Most of the time the first thread finishes before the second starts, and everything works. Under load, both threads each grab their first lock and wait for the other. In the simulator, step T1 once and T2 once and the wait cycle appears. The button that tries every interleaving shows how many of the possible orderings deadlock: a bug that fails only in some schedules is exactly why deadlocks survive testing.
Three ways to break the cycle
- Lock ordering. If every thread takes A before B, a circular wait cannot form. For dynamic locks such as two accounts, order them by a stable key such as the account id.
- tryLock with a timeout. The thread gives up, releases its locks and retries. It removes the deadlock but can livelock if both retry in lock step, so add a small random back-off.
- Less locking. Use a single lock, immutable data, or a concurrent structure so there is nothing to order.
Starvation without a lock
A fixed pool with three threads and three tasks that each submit a sub-task to the same pool and call get() will hang. Every thread is parked waiting for a result, and the sub-tasks sit in the queue with no thread to run them. No monitor is involved, so jstack does not report a deadlock, which makes this one harder to spot. The fixes are in the tab: a separate pool for the inner work, or composing the futures so no thread ever waits. Watch the queue size and the number of WAITING pool threads in production.
Learn the concepts
Race conditions and deadlock ยทLocks ยทsynchronized ยทExecutors and thread pools ยทCompletableFuture ยทThread dump analyzer
Frequently asked questions
What causes a Java deadlock?
Two or more threads each hold a lock that another one needs and wait for it forever. All four conditions must hold: mutual exclusion, hold and wait, no preemption and a circular wait. The usual cause is taking the same locks in different orders in different code paths.
How do I prevent deadlocks?
Always acquire locks in one global order, hold locks for as short a time as possible, avoid calling unknown code (callbacks, overridable methods) while holding a lock, and use tryLock with a timeout when you cannot control the order.
What is thread-pool starvation?
It is a deadlock without locks. Tasks that run in a fixed-size pool submit more tasks to the same pool and block waiting for their results. When every thread is occupied by a waiting task, the queued tasks that could unblock them never get a thread.
How do I fix starvation with nested submit?
Do not block a pool thread on work that needs the same pool. Use a separate pool for the inner work, make the pool larger than the number of concurrent outer tasks, or compose futures with thenCompose and thenApply so no thread waits.
How can I recognise these problems in production?
Take a thread dump with jstack or jcmd Thread.print. Deadlocks are reported as "Found one Java-level deadlock". Starvation shows many pool threads WAITING in Future.get with a growing queue and little CPU use.