The diagram shows a classic circular wait deadlock:
-
pool-1-thread-3holdsProductUtil.class -
AWT-EventQueue-0holds_Stock instance - Each thread is waiting for the lock held by the other
So the cycle is:
Thread A:
synchronized(ProductUtil.class) {
synchronized(stockInstance) { ... }
}
Thread B:
synchronized(stockInstance) {
synchronized(ProductUtil.class) { ... }
}
That ordering difference creates the deadlock.
Root Cause
Two locks are acquired in different orders:
| Thread | First Lock | Second Lock |
|---|---|---|
pool-1-thread-3 |
ProductUtil.class |
_Stock instance |
AWT-EventQueue-0 |
_Stock instance |
ProductUtil.class |
When both happen concurrently:
- Thread A waits for
_Stock - Thread B waits for
ProductUtil.class - Neither can continue
Best Fix: Enforce Consistent Lock Ordering
Always acquire locks in the same order everywhere.
For example:
synchronized(ProductUtil.class) {
synchronized(stockInstance) {
// work
}
}
Then NEVER do the reverse elsewhere.
Example Refactor
BAD
// Thread A
synchronized(ProductUtil.class) {
synchronized(stock) {
update();
}
}
// Thread B
synchronized(stock) {
synchronized(ProductUtil.class) {
refresh();
}
}
GOOD
// Everywhere in app
synchronized(ProductUtil.class) {
synchronized(stock) {
update();
}
}
Better Solution: Reduce Nested Locks
Nested synchronization is the biggest deadlock risk.
Instead of:
synchronized(ProductUtil.class) {
synchronized(stock) {
...
}
}
Try:
StockData data;
synchronized(stock) {
data = stock.copy();
}
ProductUtil.process(data);
This minimizes lock scope.
Important Observation from Diagram
One thread is:
AWT-EventQueue-0
That is the Swing UI thread.
This is especially dangerous because:
- UI freezes
- App becomes unresponsive
- Deadlock affects entire GUI
You should avoid heavy synchronized work on the EDT (Event Dispatch Thread).
Move work to background threads:
SwingWorker
ExecutorService
CompletableFuture
Strong Recommendation
Avoid locking on:
ProductUtil.class
Class-level locks are global and easy to deadlock.
Prefer private lock objects:
private static final Object PRODUCT_LOCK = new Object();
Then:
synchronized(PRODUCT_LOCK) {
}
Advanced Safer Alternative
Use ReentrantLock with timeout:
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// work
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
This prevents permanent deadlock.
How to Confirm in Thread Dump
Look for lines like:
Found one Java-level deadlock:
And:
waiting to lock monitor
locked monitor
You’ll see the exact cycle shown in your diagram.
Tools:
jstackjconsoleVisualVM-
JMC/Java Mission Control
Most Practical Immediate Fix
- Find all synchronized blocks involving:
ProductUtil.class_Stock
Standardize acquisition order
Remove nested locks where possible
Move synchronized logic off Swing EDT
That resolves this specific deadlock pattern almost every time.
Top comments (0)