Java Multithreading Basics-13: An article explaining the causes and solutions of deadlocks

A deadlock is a situation where multiple threads are blocked at the same time, one or all of them waiting for a resource to be released. Because the thread is blocked indefinitely, the program cannot terminate gracefully.

Table of contents

1. Several situations of deadlock

1. One thread, one lock (the same thread locks the same object twice above)

2. Two threads, two locks

3. N threads and M locks

2. Four necessary conditions for causing deadlock⭐

3. How to avoid deadlock

1. Locking sequence-break the loop waiting

2. Resource allocation strategy-Banker’s algorithm (omitted)

3. Timeout mechanism

4. Deadlock detection and recovery

5. Avoid sharing resources

4. Interview question-deadlock


1. Several situations of deadlock

1. One thread, one lock (the same thread locks the same object twice)

Reentrant locks are fine, but non-reentrant locks may deadlock.

class BlockingQueue {
    synchronized void put(int elem){
        this.size();
        ...
    }
    
    synchronized int size() {
        ...
    }
}

Supplement: Reentrant locks and non-reentrant locks 

Reentrant lock

It is a lock that supports the same thread to acquire the lock multiple times. When a thread acquires a reentrant lock for the first time, it can acquire the lock multiple times without being blocked by the lock it holds.

For example, if there is a locking operation in a recursive function, will the lock block itself during the recursive process? If not, then the lock is a reentrant lock (reentrant locks for this reason are also called recursive locks ) .

In Java, any lock named starting with Reentrant is a reentrant lock, and all ready-made Lock implementation classes provided by the JDK , including synchronized keyword locks, are reentrant. When a reentrant lock is locked, it will determine whether the thread currently applying for the lock is already the owner of the lock. If so, it will be released directly.

No reentrant lock

It is a lock that does not allow the same thread to acquire the lock multiple times. When a thread acquires a non-reentrant lock for the first time, it will be blocked by the lock it holds when it tries to acquire the lock again. If the same thread tries to acquire the lock again after acquiring the non-reentrant lock, it will cause a deadlock by "locking itself". 

2. Two threads, two locks

Even reentrant locks can deadlock. As shown in the figure below, t1 and t2 are executed concurrently. t1 locks locker1 first, and t2 locks locker2 first; t1 continues execution and locks locker2 again, but must wait for t2 to release locker2 first; t2 continues execution, and locks locker1 again, but must wait for t1 to release first locker1. A deadlock occurs at this time.

This is equivalent to going to the supermarket to buy masks without a mask during the epidemic, but the supermarket says you don’t have a mask and won’t let you in.

3. N threads and M locks

As the number of threads and locks increases, it is easier to cause deadlock.

This involves the famous "dining philosophers problem":

Obviously, if five philosophers pick up the chopsticks on the left hand side at the same time, there will be a deadlock. In other words, if a thread wants to add two locks, if one has already been added and the other is taken away, it will keep waiting while occupying the first lock.


2. Four necessary conditions for causing deadlock⭐

  1. Mutually exclusive use: that is, when a resource is used (occupied) by one thread, other threads cannot use it. [Basic characteristics of locks]
  2. Non-preemptible: Resource requesters cannot forcibly seize resources from resource occupiers, and resources can only be released actively by resource occupiers. [Basic characteristics of locks]
  3. Request and hold: that is, when a resource requester requests other resources, it maintains possession of the original resource. (Eat the food in the bowl and look at the food in the pot.) (You will not give up the previous one even if you don’t get the second chopstick.) [Characteristics of the code]
  4. Circular waiting: There is a waiting queue: P1 occupies the resources of P2, P2 occupies the resources of P3, and P3 occupies the resources of P1. This forms a waiting loop with logical dependencies. (The home key locks the car, and the car key locks the home.) (In the philosopher above, 5 waits for 4, 4 waits for 3,..., 2 waits for 1, and 1 waits for 5) [Characteristics of the code]

3. How to avoid deadlock

There are many ways to avoid deadlock, only 5 are listed here. Among them, the focus is on the first one: locking sequence.

1. Locking sequence-break the loop waiting

A deadlock is formed when the above four conditions are true. In the case of deadlock, if any of the above conditions is broken, the deadlock will disappear. One of the easiest to break is the "loop wait".

A simple way to solve the cyclic waiting situation is to number the locks. If you need to acquire multiple locks at the same time, the locking order must be to lock the small number first and then lock the large number.

As shown in the figure below, it is agreed that each philosopher can only get the chopsticks with smaller numbers in his left hand and right hand first. Philosopher No. 2 gets chopstick No. 1 first, and the remaining philosophers also get the smaller chopsticks between their left and right hands in turn. When it is the turn of the last philosopher No. 1, since chopstick No. 1 is already occupied, he cannot get chopstick 1. No. Chopsticks, while waiting to enter blocking.

While philosopher No. 1 is blocked and waiting, philosopher No. 5 can pick up chopsticks No. 5 and start eating noodles. When he completed the task, he put down the two chopsticks No. 4 and No. 5. At this time, the philosopher No. 4 could pick up the No. 4 chopsticks to eat noodles. In turn, philosophers No. 3 and No. 2 can also finish eating noodles. After philosopher No. 2 puts down his chopsticks, philosopher No. 1 can obtain chopsticks No. 1 and chopsticks No. 5, thus ending the blocking wait and starting to perform the noodle-eating task.

Therefore, as long as the locking sequence is agreed upon, the loop waiting condition will naturally be broken, and deadlock will not form. Reflected in the code, as long as multiple locks are added to a thread, the order of locking must be paid attention to. You can agree that every time you lock, the smaller number will be locked first, and then the larger number will be locked, and all threads will follow this order.

As shown in the figure below, as long as every time you lock, lock locker1 first, and then lock locker2. (Fix the order of thread locking.)

2. Resource allocation strategy-Banker’s algorithm (omitted)

Resource allocation strategies such as Banker 's Algorithm can be used to pre-evaluate the maximum demand and availability of resources to ensure that the system will not cause deadlock when allocating resources.

The essence of the banker's algorithm is a more reasonable allocation of resources. You will learn it in the school operating system class, and it is also a required question in the final exam.

But in fact, it is relatively complex. Implementing this algorithm itself may introduce additional bugs, which is not worth the loss, so it is not suitable for use in actual development. I won’t go into details here.

3. Timeout mechanism

Set a timeout for the lock acquisition operation, give up acquiring the lock after waiting for more than a certain period of time, and perform corresponding processing to avoid system blocking caused by long waiting.

4. Deadlock detection and recovery

By periodically detecting whether there is a deadlock in the system, and taking appropriate measures to recover, such as terminating certain processes or rolling back operations.

5. Avoid sharing resources

Minimize the number of shared resources between processes, or use copies instead of shared resources to avoid the possibility of deadlock caused by resource competition.


4. Interview question-deadlock

Let’s talk about what deadlock is, how to avoid deadlock, and avoid algorithms? Has it actually been solved?

Summarize:

Deadlock refers to a state in which two or more threads (or processes, only threads are described below) wait indefinitely for resources held by each other, causing the program to be unable to continue execution.

The four necessary conditions for deadlock to occur are: mutual exclusion conditions (resources can only be occupied by one thread at any time), request and hold conditions (threads holding resources are also waiting to acquire resources held by other threads), Non-preemptible conditions (threads cannot be forcibly preempted resources already occupied by other threads), circular waiting conditions (each thread is waiting for resources held by the next thread).

In order to avoid deadlock, you can adopt fixed locking sequence, adjust resource allocation strategy, set timeout for lock release, periodically detect deadlock, avoid sharing resources and other strategies. Avoidance algorithms are a method of preventing deadlocks, the most famous of which is the banker's algorithm. The banker's algorithm is based on the security of resource allocation and ensures that deadlock will not occur when allocating resources.

Guess you like

Origin blog.csdn.net/wyd_333/article/details/131737361