What is deadlock in java?

A deadlock refers to a situation where two or more threads are permanently blocked, unable to proceed with their execution because each thread is waiting for a resource that is held by another thread in the deadlock cycle. As a result, the threads end up waiting indefinitely, leading to a complete halt in program execution.

A deadlock typically occurs due to a combination of four conditions, known as the "deadlock conditions":

  1. Mutual Exclusion: At least one resource must be held in a non-sharable mode, meaning only one thread can access it at a time.
  2. Hold and Wait: A thread holding at least one resource is waiting to acquire additional resources that are currently held by other threads.
  3. No Preemption: Resources cannot be forcibly taken away from threads; they must be released voluntarily.
  4. Circular Wait: A circular chain of two or more threads exists, where each thread is waiting for a resource held by another thread in the chain.

When a deadlock occurs, none of the threads involved can make progress, resulting in a system deadlock. This situation can have severe consequences, such as application freezing, reduced performance, and potential resource wastage.

Deadlock condition

Resource1 and resource2 are used by Thread1 and Thread2

  1. Thread1 starts to use Resource1
  2. Thread1 and Thread2 try to start using resource2
  3. Thread2 'wins' and gets resource2 first
  4. now Thread2 needs to use Resource1
  5. Resource1 is already locked by Thread1, which is waiting for Thread2
The above situation create deadlock because :
  1. Thread 1 locks Resource1, waits for resource2
  2. Thread 2 locks resource2, waits for Resource1

The best way to avoid deadlocks are:

  1. avoid having locks (if possible)
  2. avoid having more than one lock
  3. always take the locks in the same order

Detecting and resolving deadlocks can be challenging. Various techniques and strategies are employed, such as analyzing thread dumps, using tools for deadlock detection, and applying proper synchronization mechanisms like avoiding nested locks and using timeouts. Additionally, designing concurrent programs with careful consideration of resource allocation and synchronization can help minimize the likelihood of encountering deadlocks.

Example
class BankAccount { double currentBalance; void withdrawAmount(double amt){ currentBalance -= amt; } void depositAmount(double amt){ currentBalance += amt; } void transfer(Account from, Account to, double amt){ sync(from); sync(to); from.withdrawAmount(amount); to.depositAmount(amount); release(to); release(from); } }

In a scenario where two threads simultaneously attempt to execute the methods transfer(a, b) and transfer(b, a), a deadlock is inevitable due to their conflicting resource acquisition order. This deadlock arises as a result of a concurrency issue, where the threads contend for shared resources in a conflicting manner.

When these threads execute their respective methods, each thread first attempts to acquire one resource and then proceeds to acquire the other. However, the conflicting order in which the resources are acquired—reverse order in this case—leads to a deadlock. This situation occurs when one thread acquires resource A and is waiting to acquire resource B, while the other thread has acquired resource B and is waiting to acquire resource A. As a result, both threads become stuck in a state of indefinite waiting, unable to proceed further, and a deadlock ensues.

Conclusion

Awareness of potential deadlocks and employing appropriate measures to handle concurrency effectively are essential for developing reliable and efficient multi-threaded Java applications.