This is the 10th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Deadlock refers to the phenomenon that two or more threads are waiting for each other during execution due to competing for resources. Without external force, these threads will wait for each other and cannot continue to run.

There are four conditions for a thread to generate a deadlock:

  • Mutually exclusive: A thread can use a resource exclusively, that is, only one thread can occupy the resource at a time. If another thread requests the resource at this point, the requester can only wait until the thread holding the resource releases the resource.
  • Request and hold condition: a thread that has already held at least one resource makes a request for a new resource, but the new resource has been occupied by another thread. Therefore, the current thread is blocked, but the blocked thread does not release the acquired resource.
  • Inalienable condition: A resource acquired by a thread cannot be preempted by other threads before being used up by the thread. The resource can be released only after being used up by the thread.
  • Loop waiting condition: when a deadlock occurs, there must be a thread-resource ring chain, that is, T0 in thread set {T0, T1, T2, Tn} is waiting for a resource occupied by T1, T1 is waiting for a resource occupied by T2, Tn is waiting for a resource occupied by T0.
Public class code_1_9_DeadLockTest {private static Object resourceA = new Object(); private static Object resourceB = new Object(); Public static void main(String[] args) throws InterruptedException{Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (resourceA) { System.out.println(Thread.currentThread() + " get ResourceA"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + " waiting get sourceB"); synchronized (resourceB) { System.out.println(Thread.currentThread() + " get resourceB"); }}}}); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (resourceB) { System.out.println(Thread.currentThread() + " get ResourceB"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + " waiting get sourceA"); synchronized (resourceA) { System.out.println(Thread.currentThread() + " get resourceA"); }}}}); Threada.start (); threadB.start(); }}Copy the code

The following output is displayed:

In the above code, two resources, resourceA and resourceB, are created first. The thread scheduler schedules thread A first, thread A obtains resourceA using synchronized, and then sleeps for 1s to ensure thread B obtains resourceB first. Thread A will try to get resourceB, and thread B will try to get resourceA. So thread A and thread B fall into A mutual waiting state, creating A deadlock.

So how does this example satisfy the four necessary conditions for deadlocks?

  1. Both resourceA and resourceB are mutually exclusive resources. Another thread can hold the resource only after the thread holding the resource is released. This satisfies the mutual exclusion condition.

  2. Thread A holds resourceA and then continues to wait for resourceB, which constitutes A request-and-hold condition.

  3. After thread A has A resourceA, the resource cannot be preempted by thread B. Only when thread A releases the resource, thread A relinquishes the right to hold the resource, which constitutes the resource inalienable condition.

  4. Thread A holds resourceA and waits to acquire resourceB, while thread B holds resourceB and waits to acquire resourceA, which constitutes the loop wait condition.

So how do you avoid deadlocks?

To avoid deadlocks, all we need to do is break a condition that makes a deadlock. Currently, however, only request-and-hold and loop waiting conditions can be broken. Deadlocks are caused by the order in which resources are applied. The principle of ordering resources can be used to avoid deadlocks. For example, in the code above, thread B requests resources in the same order as thread A.

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    System.out.println(Thread.currentThread() + " get ResourceA");

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + " waiting get sourceB");
                    synchronized (resourceB) {
                        System.out.println(Thread.currentThread() + " get resourceB"); }}}});Copy the code

The output is as follows:

In fact, the ordering of resource allocation means that thread A and thread B will sort resources when they both need resources A, B and C. Thread A and thread B can obtain resource N only when they have obtained resource n-1. In the modified code, thread A gets A resourceA, and thread B blocks instead of getting A resourceB. In this case, thread A can acquire the monitor lock resource of resourceB and relinquish its possession of resourceA. In this case, thread B can acquire resourceA.