DEV Community

Query Filter
Query Filter

Posted on

docker137

The diagram shows a classic circular wait deadlock:

  • pool-1-thread-3 holds ProductUtil.class
  • AWT-EventQueue-0 holds _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) { ... }
  }
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

GOOD

// Everywhere in app
synchronized(ProductUtil.class) {
    synchronized(stock) {
        update();
    }
}
Enter fullscreen mode Exit fullscreen mode

Better Solution: Reduce Nested Locks

Nested synchronization is the biggest deadlock risk.

Instead of:

synchronized(ProductUtil.class) {
    synchronized(stock) {
        ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Try:

StockData data;

synchronized(stock) {
    data = stock.copy();
}

ProductUtil.process(data);
Enter fullscreen mode Exit fullscreen mode

This minimizes lock scope.


Important Observation from Diagram

One thread is:

AWT-EventQueue-0
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Strong Recommendation

Avoid locking on:

ProductUtil.class
Enter fullscreen mode Exit fullscreen mode

Class-level locks are global and easy to deadlock.

Prefer private lock objects:

private static final Object PRODUCT_LOCK = new Object();
Enter fullscreen mode Exit fullscreen mode

Then:

synchronized(PRODUCT_LOCK) {
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

This prevents permanent deadlock.


How to Confirm in Thread Dump

Look for lines like:

Found one Java-level deadlock:
Enter fullscreen mode Exit fullscreen mode

And:

waiting to lock monitor
locked monitor
Enter fullscreen mode Exit fullscreen mode

You’ll see the exact cycle shown in your diagram.

Tools:

  • jstack
  • jconsole
  • VisualVM
  • JMC / Java Mission Control

Most Practical Immediate Fix

  1. Find all synchronized blocks involving:
  • ProductUtil.class
  • _Stock
  1. Standardize acquisition order

  2. Remove nested locks where possible

  3. Move synchronized logic off Swing EDT

That resolves this specific deadlock pattern almost every time.

Top comments (0)