Java multithreading (8)

Table of contents

1. Deadlock situation

1.1 One thread has multiple locks

1.1.1 Implementation mechanism of reentrant locks in Java

1.2 Two threads and two locks

1.3 N threads and M locks

2. Solution to deadlock

2.1 Necessary conditions for deadlock

2.2 Break the waiting cycle


1. Deadlock situation

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.

1.1 One thread has multiple locks

When a thread has multiple locks, it first successfully obtains the first lock, but when encountering the second lock, a blocking wait will occur. To solve this blocking wait, you need to release the lock first, and the lock release It requires thread entry. It's like trying to open the door, but the key is inside the room. This process results in a deadlock of waiting results.

synchronized (locker) {
    synchronized (locker) {
        System.out.println("一个线程多把锁");
    }
}

However, this problem will not cause a deadlock in Java, because synchronized is a reentrant lock and can flexibly determine whether the lock has been added, so that a deadlock will not occur. But the lock in C++ is a non-reentrant lock, and the way this code is written will produce a deadlock.

1.1.1 Implementation mechanism of reentrant locks in Java

Since the synchronized lock in Java is a reentrant lock, it will not be repeatedly locked when a thread has multiple locks. However, this leads to another problem. The lock should not be released during the intermediate locking process. Yes, then how does the jvm know when to release the lock?

The sychronized solution is to use a counter. When a lock operation is encountered, it is +1, and when an unlock operation is encountered, it is -1. When the counter reaches 0, the lock is actually released.

1.2 Two threads and two locks

If two threads acquire a lock each and then acquire the other's lock, a deadlock will occur.

public class Demo21 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(()->{
           synchronized (locker1) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               synchronized (locker2) {
                   System.out.println("对locker2对象加锁");
               }
           }
        });
        Thread t2 = new Thread(()->{
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker1) {
                    System.out.println("对locker1对象加锁");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

When the program runs here, a deadlock will occur. When locker1 here wants to acquire locker2, since locker2 has already been locked, it needs to wait for locker2 to release the lock. However, when locker2 releases the lock, it needs to acquire locker1's lock, and locker1 is also in A locked state. It's like coming home and finding the house keys in the car, and then the car keys at home. This results in waiting until it becomes a deadlock.

1.3 N threads and M locks

"Philosophers Dining Problem", here we introduce 5 philosophers and 5 chopsticks to correspond to the situation of 5 threads and 5 locks. If each philosopher wants to eat, he needs to pick up the chopsticks in his left hand and then the chopsticks in his right hand. If other philosophers want to eat, they can only wait for one of the chopsticks to be put down. 

Under normal circumstances, there will be no conflict, but when five philosophers pick up the chopsticks in their left hands at the same time, there will be a situation of waiting. This will cause a deadlock.

2. Solution to deadlock

2.1 Necessary conditions for deadlock

1. Mutually exclusive use: one thread acquires this lock, and other threads cannot acquire this lock.

2. Non-preemptible: The lock can only be actively released by the previous holder and cannot be snatched away by other threads.

3. Request and hold: When a thread acquires multiple locks, it will maintain the acquisition status of the previous lock.

4. Circular waiting: When you want to acquire a held lock, you need to wait until the holder releases the lock.

After understanding the necessary conditions for deadlock, the deadlock problem can be solved by breaking one of them. However, the first two conditions are both necessary conditions for deadlock and characteristics of locks, so these two conditions cannot be broken. Therefore, the only way to solve the deadlock problem is 3 and 4. However, if you want to solve the condition of 3, you need to change the logic of most of the code, and the cost is very high. Therefore, in order to solve the deadlock problem, the best solution is to break the condition of loop waiting.

2.2 Break the waiting cycle

The Banker's algorithm can solve the deadlock problem, but it is more complicated and may cause more complex problems in the process of solving the deadlock problem. Therefore, it is not recommended to use the Banker's algorithm to generally solve the deadlock problem.

A simple and effective method is introduced here to solve the deadlock problem: number the locks and specify the lock order . For example, if each thread wants to acquire multiple locks, it needs to acquire the lock with the smallest number first.

Similarly, modifying the situation of two threads and two locks according to this situation will solve the deadlock problem.

public class Demo21 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(()->{
           synchronized (locker1) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               synchronized (locker2) {
                   System.out.println("对locker2对象加锁");
               }
           }
        });
        Thread t2 = new Thread(()->{
            synchronized (locker1) {//先获取编号顺序小的锁
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker2) {
                    System.out.println("对locker1对象加锁");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

Run it again and find that the deadlock problem has been solved.

Guess you like

Origin blog.csdn.net/x2656271356/article/details/132206146