How to Break Java’s Deadlock: From Coffman Conditions to Practical Lock Strategies
This article explains why naive synchronized locks can cause deadlocks in Java, introduces the four Coffman conditions, and presents three practical solutions—acquiring all resources at once, using explicit locks with wait/notify, and ordering lock acquisition—to prevent deadlock in concurrent applications.
Understanding Java Deadlock
When multiple threads use synchronized methods without careful design, they can enter a deadlock where each thread waits indefinitely for a resource held by another.
Coffman Conditions
Four conditions must hold simultaneously for a deadlock to occur: mutual exclusion, hold and wait, no preemption, and circular wait.
Java’s built‑in synchronized lock can lead to “dead waiting” because it never releases a lock voluntarily.
Solution 1: Acquire All Required Resources at Once
Introduce an AccountBookManager that grants both source and target accounts together; if both are not available, the thread retries.
class Account {
private int balance;
// transfer
void transfer(Account target, int amt) {
synchronized (this) {
synchronized (target) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
}Improved version with manager:
class AccountBookManager {
synchronized boolean getAllRequiredAccountBook(Object from, Object to) {
// return true only when both books are obtained
}
synchronized void releaseObtainedAccountBook(Object from, Object to) { /* ... */ }
}
class Account {
private AccountBookManager manager;
void transfer(Account target, int amt) {
while (!manager.getAllRequiredAccountBook(this, target)) {
return;
}
try {
synchronized (this) {
synchronized (target) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
} finally {
manager.releaseObtainedAccountBook(this, target);
}
}
}Solution 2: Break Hold‑and‑Wait
Use explicit locks (java.util.concurrent.locks.Lock) with tryLock, wait/notify, or timeout to avoid indefinite waiting.
Solution 3: Impose a Global Lock Ordering
Assign a unique identifier to each account and always lock the lower‑id account first, eliminating circular wait.
class Account {
private int id;
private int balance;
void transfer(Account target, int amt) {
Account smaller = this.id < target.id ? this : target;
Account larger = this.id < target.id ? target : this;
synchronized (smaller) {
synchronized (larger) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
}In real systems, database transactions, optimistic locking, or distributed locks (e.g., Redis) can also prevent deadlocks.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
