[Kill Thread Part.3-1] the concept of deadlock

Posted by ILMV on Sat, 05 Feb 2022 05:09:36 +0100

[Kill Thread Part.3-1] the concept of deadlock

1, What is a deadlock

1. Deadlock diagram

Occurs in concurrency

Intransigence: when two (or more) threads (or processes) hold the resources needed by each other and do not actively release them, everyone can not continue to move forward, resulting in endless blocking of the program, which is deadlock.

2. Effect of deadlock

The effect of deadlock is different in different systems, which depends on the ability of the system to deal with deadlock.

  • In database: detect and discard transactions
  • In JVM: cannot be processed automatically

characteristic:

  • The probability is not high, but the harm is great
  • Once it happens, it is mostly a high concurrency scenario, which affects many users
  • Whole system crash, subsystem crash and performance degradation
  • Stress testing cannot find all potential deadlocks

2, Examples of deadlock

1. The simplest case

code

/**
 * Description: a deadlock must occur
 */
public class MustDeadLock implements Runnable{
    int flag = 1;

    //Static object lock
    static Object o1 = new Object();
    static Object o2 = new Object();

    @Override
    public void run() {
        System.out.println("flag = " + flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("Thread 1 successfully got two locks");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("Thread 2 successfully got two locks");
                }
            }
        }
    }

    public static void main(String[] args) {
        MustDeadLock r1 = new MustDeadLock();
        MustDeadLock r2 = new MustDeadLock();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
    }
}
  • When the class object flag = 1 (T1), lock O1 first, sleep for 500 milliseconds, and then lock O2;

  • When T1 is sleeping, another object (T2) thread with flag = 0 starts, locks O2 first, sleeps for 500 milliseconds, and waits for T1 to release O1;

  • After T2 sleep, O1 needs to be locked to continue execution, and O1 has been locked by T1 at this time;

  • T1 and T2 wait for each other, and both need the resources locked by the other party to continue to execute, resulting in deadlock.

2. Example in actual production: transfer

  • Two locks are needed
  • If two locks are obtained successfully and the balance is greater than 0, the transferor will be deducted and the balance of the payee will be increased. This is an atomic operation.
  • Reverse order causes deadlock

code

/**
 * Description: a deadlock is encountered during transfer. Once the comment is opened, the deadlock will occur
 */
public class TransferMoney implements Runnable{
    int flag = 1;
    static Account a = new Account(500);
    static Account b = new Account(500);

    @Override
    public void run() {
        if (flag == 1) {
            //a transfers 200 yuan to b
            transferMoney(a, b, 200);
        }
        if (flag == 0) {
            //b transferred to a200 yuan
            transferMoney(b, a, 200);
        }
    }

    public static void transferMoney(Account from, Account to, int amount) {
        //Get two locks
        synchronized (from) {
            //try {
            //    Thread.sleep(500);
            //} catch (InterruptedException e) {
            //    e.printStackTrace();
            //}
            synchronized (to) {
                if (from.balance - amount < 0) {
                    System.out.println("Insufficient balance, transfer failed.");
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println("Successful transfer" + amount + "element");
            }
        }
    }
    static class Account {
        int balance;//balance

        public Account(int balance) {
            this.balance = balance;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("a Balance of" + a.balance);
        System.out.println("b Balance of" + b.balance);
    }
}

After adding comments, a thread obtains two locks and runs the result

However, if the comment is removed, the thread first obtains one lock and then waits for another lock, and the order in which another thread obtains the lock is opposite, resulting in deadlock.

3. Simulated multi person random transfer: Circular deadlock

code

package deadlock;

import deadlock.TransferMoney.Account;
import java.util.Random;

public class MultiTransferMoney {
    //Number of accounts
    private static final int NUM_ACCOUNTS = 500;
    //Account amount
    private static final int NUM_MONEY = 1000;
    //Transfer times
    private static final int NUM_ITERATIONS = 1000000;
    //Number of threads executing simultaneously
    private static final int NUM_THREADS = 20;
    public static void main(String[] args) {
        
        Random rnd = new Random();
        Account[] accounts = new Account[NUM_ACCOUNTS];
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = new Account(NUM_MONEY);
        }

        class TransferThread extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < NUM_ITERATIONS; i++) {
                    int fromAcct = rnd.nextInt(NUM_ACCOUNTS);
                    int toAcct = rnd.nextInt(NUM_ACCOUNTS);
                    int amount = rnd.nextInt(NUM_MONEY);
                    TransferMoney.transferMoney(accounts[fromAcct], accounts[toAcct], amount);
                }
            }
        }

        for (int i = 0; i < NUM_THREADS; i++) {
            new TransferThread().start();
        }
    }
}

Code based evolution 2. A circular deadlock problem has occurred.

  • There are a lot of 50000 people, but there will still be deadlock, Murphy's law
  • The probability of deadlock is not high, but it is very harmful.

3, Four necessary conditions for deadlock

  • mutual exclusion
    • A resource can only be used by the same process or thread at a time, such as a lock.
    • If a resource is shared infinitely, it does not meet the mutual exclusion condition
  • Request and hold conditions
    • For example, the first thread requests the second lock, but I keep my first lock.
    • At this time, when a request occurs, I block myself, so I do not release the lock for the resources I have obtained.
  • Conditions of non deprivation
    • It cannot be interfered by the outside world to deprive the thread of the lock.
  • Cycle waiting condition
    • Two threads are you waiting for me, I wait for you.
    • Multiple threads constitute a loop and deadlock occurs.

Both are indispensable and satisfied at the same time.

4, How to locate deadlocks

1. jps and jstack applications

result

2. ThreadMXBean code demonstration

package deadlock;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/**
 * Description: use ThreadMXBean to detect deadlock
 */
public class ThreadMXBeanDetection implements Runnable {

    int flag = 1;

    static Object o1 = new Object();
    static Object o2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
        ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        //Code for deadlock discovery
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        if (deadlockedThreads != null && deadlockedThreads.length > 0) {
            for (int i = 0; i < deadlockedThreads.length; i++) {
                ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
                System.out.println("deadlock detected" + threadInfo.getThreadName());
            }
        }
    }

    @Override
    public void run() {
        System.out.println("flag = " + flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("Thread 1 successfully got two locks");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("Thread 2 successfully got two locks");
                }
            }
        }
    }
}

5, Strategies for fixing deadlocks

1. What should I do when thinking online?

  • Online problems should be nipped in the bud. It is almost impossible to extinguish them without causing losses.
  • Save the crime scene and restart the server immediately
  • Temporarily ensure the security of online services, and then use the information just saved to check the deadlock, modify the code and reissue the version.

2. Common repair strategies

  • Avoidance strategy:
    • The exchange scheme and transfer order exchange scheme of philosophers' dining
  • Detection and recovery strategy:
    • Detect whether there is a deadlock for a period of time. If there is, deprive a resource to open the deadlock
  • Ostrich strategy:
    • When an ostrich is in danger, it usually buries its head on the ground so that it can't see the danger. The ostrich strategy means that if the probability of deadlock is extremely low, we will ignore it directly and repair it manually when the deadlock occurs.

3. Deadlock avoidance strategy

  • Idea: avoid the reverse order of obtaining locks
    • Avoid deadlock during transfer
    • Actually, I don't care about the order in which locks are acquired

Modification of transfer code

  • The order of obtaining locks is determined through hashCode, and "overtime" is required in case of conflict
/**
 * Description: a deadlock is encountered during transfer. Once the comment is opened, the deadlock will occur
 */
public class TransferMoney implements Runnable {

    int flag = 1;
    static Account a = new Account(500);
    static Account b = new Account(500);
    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("a Balance of" + a.balance);
        System.out.println("b Balance of" + b.balance);
    }

    @Override
    public void run() {
        if (flag == 1) {
            transferMoney(a, b, 200);
        }
        if (flag == 0) {
            transferMoney(b, a, 200);
        }
    }

    public static void transferMoney(Account from, Account to, int amount) {
        class Helper {

            public void transfer() {
                if (from.balance - amount < 0) {
                    System.out.println("Insufficient balance, transfer failed.");
                    return;
                }
                from.balance -= amount;
                to.balance = to.balance + amount;
                System.out.println("Successful transfer" + amount + "element");
            }
        }
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);
        if (fromHash < toHash) {
            synchronized (from) {
                synchronized (to) {
                    new Helper().transfer();
                }
            }
        }
        else if (fromHash > toHash) {
            synchronized (to) {
                synchronized (from) {
                    new Helper().transfer();
                }
            }
        }else  {//If the hash conflicts, who gets the lock first and transfers the money first
            synchronized (lock) {
                synchronized (to) {
                    synchronized (from) {
                        new Helper().transfer();
                    }
                }
            }
        }

    }


    static class Account {

        public Account(int balance) {
            this.balance = balance;
        }

        int balance;

    }
  • It is more convenient to have a primary key in the database. The order of obtaining locks can be determined by the level of the primary key.

4. Dining problem of philosophers

Problem description

  • First pick up the chopsticks in your left hand
  • Then pick up the chopsticks in your right hand
  • If chopsticks are used, wait for others to run out
  • After eating, put the chopsticks back in place

There is a risk of deadlock and resource exhaustion

Deadlock: every philosopher holds the chopsticks in his left hand and always waits for the chopsticks on the right (or vice versa)

Code demonstration: philosopher enters deadlock

/**
 * Description: demonstrates deadlock caused by dining problems
 */
public class DinningPhilosophers {
    public static class Philosopher implements Runnable {
        private Object leftChopstick;
        private Object rightChopstick;

        public Philosopher(Object lestChopstick, Object rightChopstick) {
            this.leftChopstick = lestChopstick;
            this.rightChopstick = rightChopstick;
        }
        @Override
        public void run() {
            try {
                while (true) {
                    //reflection
                    doAction("Thinking");
                    //Take the chopsticks on the left
                    synchronized (leftChopstick) {
                        doAction("Picked up left chopstick");
                        synchronized (rightChopstick) {
                            doAction("Picked up right chopstick - eating");

                            doAction("Put down right chopstick");
                        }
                        doAction("Put down left chopstick");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        private void doAction(String action) throws InterruptedException {
            System.out.println(Thread.currentThread().getName() + " " + action);
            Thread.sleep((long) Math.random() * 10);
        }
    }

    public static void main(String[] args) {
        Philosopher[] philosophers = new Philosopher[5];
        Object[] chopsticks = new Object[philosophers.length];

        //Initialize chopsticks
        for (int i = 0; i < chopsticks.length; i++) {
            chopsticks[i] = new Object();
        }
        //Initialization philosopher
        for (int i = 0; i < philosophers.length; i++) {
            Object leftChopstick = chopsticks[i];
            //Prevent cross-border circulation
            Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];
            philosophers[i] = new Philosopher(leftChopstick, rightChopstick);

            new Thread(philosophers[i], "philosopher" + (i + 1) + "number").start();
        }
    }
}

Result demonstration: deadlock

Philosophers picked up the chopsticks on their left hand side and did not release them.

Multiple solutions

  • Attendant check (avoidance strategy)
  • Change the order in which a philosopher holds a fork (avoidance strategy)
  • Meal ticket (avoidance strategy)
  • Leadership adjustment (detection and recovery strategy)

Code demonstration: solving deadlock

To the last philosopher, the order of getting chopsticks is the opposite, breaking the loop.

5. Deadlock detection algorithm: call link graph of lock

  • Allow deadlock

  • Each call lock is recorded and maintained with a directed graph

  • Regularly check whether there is a loop in the "call link diagram of lock"

  • In case of deadlock, the following deadlock recovery mechanism shall be adopted:

    • Process termination
      • Terminate threads one by one until the deadlock is eliminated.
      • Termination sequence:
        • The importance of priority (foreground interaction or background processing) to the program
        • Occupied and needed resources
        • Elapsed time
    • resource preemption
      • Take back the locks that have been distributed
      • Let the thread go back a few steps, so you don't have to end the whole thread, and the cost is relatively low
      • Disadvantages: the same thread may be preempted all the time, causing hunger

6, How to avoid deadlock in practical engineering

1. Set timeout

  • Try Lock: tryLock(long timeout, TimeUnit unit)
    • synchronized does not have the ability to attempt to acquire a lock
    • If the lock is obtained: log, send alarm email, restart, etc.

Code demonstration: take a step back

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Description: use tryLock to avoid deadlock
 */
public class TryLockDeadlock implements Runnable{
    int flag = 1;
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();
    
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (flag == 1) {
                try {
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                        System.out.println("Thread 1 acquired lock 1");
                        //Simulate the real situation
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                            System.out.println("Thread 1 acquired lock 2");
                            System.out.println("Thread 1 successfully acquired two locks");
                            lock2.unlock();
                            lock1.unlock();
                            break;
                        } else {
                            System.out.println("Thread 1 failed to acquire lock 2 and has been retried");
                            //It's important to release
                            lock1.unlock();
                            System.out.println("Thread 1 abandoned lock 1");
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    }else {
                        System.out.println("Thread 1 failed to acquire lock 1, retried");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (flag == 0) {
                try {
                    if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) {
                        System.out.println("Thread 2 acquired lock 2");
                        //Simulate the real situation
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock1.tryLock(3000, TimeUnit.MILLISECONDS)) {
                            System.out.println("Thread 2 acquired lock 1");
                            System.out.println("Thread 2 successfully acquired two locks");
                            lock1.unlock();
                            lock2.unlock();
                            break;
                        } else {
                            System.out.println("Thread 2 failed to attempt to acquire lock 1 and has retried");
                            //It's important to release
                            lock2.unlock();
                            System.out.println("Thread 2 abandoned lock 2");
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    }else {
                        System.out.println("Thread 2 failed to acquire lock 2, retried");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        TryLockDeadlock r1 = new TryLockDeadlock();
        TryLockDeadlock r2 = new TryLockDeadlock();
        r1.flag = 1;
        r2.flag = 0;
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

Operation results:

2. Use more concurrent classes instead of designing locks yourself

3. Other

  • Minimize the granularity of locks: use different locks instead of one

  • If you can use synchronized code blocks, the synchronization method is not applicable: make your own lock object

  • Avoid nesting locks: MustDeadLock class

  • See if you can get it back before allocating resources: banker algorithm

  • Try not to use the same lock for several functions: special lock

7, Other active faults

Deadlock is the most common activity problem. In addition to deadlock, there are some similar problems that will cause the program to fail to execute smoothly, which are collectively referred to as activity problems.

1. Livelock

① What is a live lock

  • Although the thread is not blocked and is always running (so it is called "live" lock, and the thread is "live"), the program does not progress because the thread always does the same thing repeatedly.
  • If there is a deadlock here, then there are two people here who remain motionless until the other party looks up first. They don't talk anymore, just wait.

② Livelock sample code

Cowherd and Weaver share a spoon for dinner and are humble to each other.

import java.util.Random;
import jdk.management.resource.internal.inst.RandomAccessFileRMHooks;

/**
 * Description: demonstrate the livelock problem
 */
public class LiveLock {

    static class Spoon {

        private Diner owner;

        public Spoon(Diner owner) {
            this.owner = owner;
        }

        public Diner getOwner() {
            return owner;
        }

        public void setOwner(Diner owner) {
            this.owner = owner;
        }

        public synchronized void use() {
            System.out.printf("%s I'm full!", owner.name);


        }
    }

    static class Diner {

        private String name;
        private boolean isHungry;

        public Diner(String name) {
            this.name = name;
            isHungry = true;
        }

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                if (spoon.owner != this) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                Random random = new Random();
                //Join the backoff algorithm to avoid being humble all the time
                if (spouse.isHungry && random.nextInt(10) < 9) {
                    System.out.println(name + ": dear" + spouse.name + "You eat first");
                    spoon.setOwner(spouse);
                    continue;
                }

                spoon.use();
                isHungry = false;
                System.out.println(name + ": I'm full");
                spoon.setOwner(spouse);

            }
        }
    }


    public static void main(String[] args) {
        //Two diners
        Diner husband = new Diner("a cowboy");
        Diner wife = new Diner("woman weaver");

        //Share a spoon
        Spoon spoon = new Spoon(husband);

        new Thread(new Runnable() {
            @Override
            public void run() {
                husband.eatWith(spoon, wife);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                wife.eatWith(spoon, husband);
            }
        }).start();
    }
}
  • Add random factors to avoid livelock.

③ Livelock instance in project: message queue

2. Starvation

8, Common interview questions

  • Handwriting is an example that will inevitably fall into deadlock. What scenario will deadlock occur in production?

  • What conditions must be met for a life and death lock?

  • How to locate deadlocks?

  • What are the strategies to solve the deadlock problem?

  • Talk about the classic dining problem of philosophers

    • Give a variety of Solutions
      • Waiter check
      • Change the order of holding knives and forks
      • Meal ticket (avoidance strategy)
      • deprive
  • How to avoid deadlock in practical engineering?

  • What is the activity problem?

    • What's the difference between live lock, hunger and deadlock?