Chapter 13: concurrent deadlock and enterprise solutions (deadlock, livelock, starvation)

Posted by ollmorris on Mon, 29 Jun 2020 08:23:19 +0200

1. What is a deadlock? What's the harm?

1.1 what is deadlock?

  • Occurs in concurrency
  • [mutually exclusive]: when two (or more) threads (or processes) hold each other's resources required by each other, but do not actively release them, all of them can't move forward, resulting in endless blocking of the program, which is called deadlock.  
  • Deadlock caused by multiple threads (a - > b - > C - > A)

1.2 impact of deadlock

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

  • In the database: deadlock is detected (two transactions AB compete with each other), one of transaction A will be abandoned, and let B execute first, and then execute A
  • In JVM: cannot be handled automatically

1.3 the probability is not high, but the harm is great

  • It doesn't have to happen, but follow Murphy's Law (if something is likely to go bad, no matter how small it is, it always happens)
  • Once it happens, it is mostly a high concurrency scenario, which affects many users
  • The whole system crash, subsystem crash, performance degradation
  • Stress testing failed to find all potential deadlocks

2. Examples of deadlock

2.1 simplest case

code

/**
 * MustDeadLock
 *
 * @author venlenter
 * @Description: Deadlock is bound to occur
 * @since unknown, 2020-06-10
 */
public class MustDeadLock implements Runnable {
    int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

    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();
    }
    @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 gets two locks successfully");
                }
            }
        }
        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");
                }
            }
        }
    }
}

//Output results
flag = 1
flag = 0
//The thread has not been interpreted and is in a deadlock state

analysis

  • T1 and T2 [wait for each other], both of them need the resources locked by the other party to continue execution, thus causing deadlock
  • Force abort program, IDEA will print one more line (code -1)
flag = 1
flag = 0

Process finished with exit code -1
  • Non-0 is the abnormal exit signal, and the [end signal is 0] of the normal ending program

2.2 example in actual production: transfer

  • Two locks are needed
  • If two locks are obtained successfully and the balance is greater than 0, then deduct the transferor and increase the balance of the payee. This is an atomic operation
  • Deadlock due to reverse order
/**
 * TransferMoney
 *
 * @author venlenter
 * @Description: Deadlock occurs during transfer. Once the comment is opened, deadlock will occur
 * @since unknown, 2020-06-13
 */
public class TransferMoney implements Runnable {
    int flag = 1;
    static Account a = new Account(500);
    static Account b = new Account(500);

    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 The balance of" + a.balance);
        System.out.println("b The 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) {
        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 {
        public Account(int balance) {
            this.balance = balance;
        }

        int balance;
    }
}

//Output results (without deadlock)
//Successful transfer of 200 yuan
//Successfully transferred 200 yuan
a The balance of 500
b The balance of 500

//Open notes Thread.sleep Then a and b threads are deadlock, no output, waiting for each other

2.3 simulation of multi person random transfer

  • There are many 5W people, but there will still be deadlock, Murphy's law
  • The probability of life and death lock is not high, but the harm is great
/**
 * MultiTransferMoney
 *
 * @author venlenter
 * @Description: It's still very dangerous for many people to transfer money at the same time
 * @since unknown, 2020-06-13
 */
public class MultiTransferMoney {
    private static final int NUM_ACCOUNTS = 500;
    private static final int NUM_MONEY = 1000;
    private static final int NUM_THREADS = 20;
    private static int NUM_ITERATIONS = 1000000;

    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);
                }
                System.out.println("End of run");
            }
        }
        for (int i = 0; i < NUM_THREADS; i++) {
            new TransferThread().start();
        }
    }
}
//Output results (after a certain period of time, 20 threads are stuck, deadlock)
//Successful transfer of 568 yuan
//Successful transfer of 129 yuan
//Successful transfer of 225 yuan
//Successful transfer of 623 yuan
...
//Successful transfer of 889 yuan
//Insufficient balance, transfer failed
//Successfully transferred RMB 451
//Insufficient balance, transfer failed
//Successfully transferred RMB 138
//All threads stuck

3. Four necessary conditions of deadlock

  • Mutex condition (if thread A gets lock-a, other threads can only wait for lock-a)
  • Request and hold condition (thread A keeps lock-a lock when it requests lock-b)
  • No deprivation condition (thread A holds lock-a, and the outside world cannot deprive A of lock-a)
  • Loop wait condition (multiple threads form A loop, A waits for B, B waits for C, C waits for A)

4. How to locate deadlock

4.1 use the java command jstack (${JAVA_HOME}/bin/jstack pid)

  • example
1 package ConcurrenceFolder.mooc.threadConcurrencyCore.deadlock;
2 
3 /**
4 * MustDeadLock
5 *
6 * @author venlenter
7 * @Description: Deadlock is bound to occur
8 * @since unknown, 2020-06-10
9 */
10 public class MustDeadLock implements Runnable {
11    int flag = 1;
12    static Object o1 = new Object();
13    static Object o2 = new Object();
14
15    public static void main(String[] args) {
16        MustDeadLock r1 = new MustDeadLock();
17        MustDeadLock r2 = new MustDeadLock();
18        r1.flag = 1;
19        r2.flag = 0;
20        Thread t1 = new Thread(r1);
21        Thread t2 = new Thread(r2);
22        t1.start();
23        t2.start();
24    }
25    @Override
26    public void run() {
27        System.out.println("flag = " + flag);
28        if (flag == 1) {
29            synchronized (o1) {
30                try {
31                    Thread.sleep(500);
32                } catch (InterruptedException e) {
33                    e.printStackTrace();
34                }
35                synchronized (o2) {
36                    System.out.println("Thread 1 successfully got two locks");
37                }
38            }
39        }
40        if (flag == 0) {
41            synchronized (o2) {
42                try {
43                    Thread.sleep(500);
44                } catch (InterruptedException e) {
45                    e.printStackTrace();
46                }
47                synchronized (o1) {
48                    System.out.println("Thread 2 successfully got two locks");
49                }
50            }
51        }
52    }
53}

  • Find the process pid of the program executed above, and execute it: D: program files / Java / jdk1.8.0_ 172\bin> jstack.exe one hundred and eight thousand three hundred and fifty-two
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x000000001be53948 (object 0x0000000780caf9a0, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x000000001a93cd18 (object 0x0000000780caf9b0, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at ConcurrenceFolder.mooc.threadConcurrencyCore.deadlock.MustDeadLock.run(MustDeadLock.java:48)
        - waiting to lock <0x0000000780caf9a0> (a java.lang.Object)
        - locked <0x0000000780caf9b0> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at ConcurrenceFolder.mooc.threadConcurrencyCore.deadlock.MustDeadLock.run(MustDeadLock.java:36)
        - waiting to lock <0x0000000780caf9b0> (a java.lang.Object)
        - locked <0x0000000780caf9a0> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.
  • Thread1 lock f9b0,waiting f9a0
  • Thread0 lock f9a0,waiting f9b0
  • It also shows the location of the deadlock MustDeadLock.java:48 and MustDeadLock.java:36

4.2 ThreadMXBean code detection

**
 * MustDeadLock
 *
 * @author venlenter
 * @Description: use ThreadMXBean Detection algorithm 
 * @since unknown, 2020-06-10
 */
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);
        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 found:" + 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 gets two locks successfully");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("Thread 2 gets two locks successfully");
                }
            }
        }
    }
}

//Output results
flag = 1
flag = 0
//Deadlock found: Thread-1
//Deadlock found: Thread-0

5. Strategy of repairing deadlock

5.1 what should I do to send life and death locks online?

  • Online problems need to be prevented in advance, and it is almost impossible to put out without loss
  • Save the scene and restart the server immediately
  • Ensure the security of the online service temporarily, and then use the information saved just now to check the deadlock, modify the code, and reissue

5.2 common repair strategies

  • Avoidance strategy: the scheme of changing hands and changing the order of money transfer in the dining of philosophers (idea: avoid the reverse order of acquiring locks)
  • Detection and recovery strategy: detect whether there is a deadlock for a period of time, and if there is, deprive a resource to open the deadlock

5.2.1 avoid deadlock during transfer (transfer order change scheme)

  • Doesn't really care about the order in which the locks are acquired
  • Code demonstration
  • Use [hashcode] to determine the order of lock acquisition, and "overtime game" is required in case of conflict
/**
 * TransferMoney
 *
 * @author venlenter
 * @Description: During transfer, the order of obtaining lock is determined by [hashcode] to avoid deadlock
 * @since unknown, 2020-06-13
 */
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 The balance of" + a.balance);
        System.out.println("b The 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) {
        //Add inner class
        class Helper {
            public void transfer() {
                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");
            }
        }
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);
        //Through the [hashcode] to determine the order of lock acquisition
        if (fromHash < toHash) {
            synchronized (from) {
                synchronized (to) {
                    new Helper().transfer();
                }
            }
        } else if (fromHash > toHash) {
            synchronized (to) {
                synchronized (from) {
                    new Helper().transfer();
                }
            }
        } else {
            //When hashcode s are the same, extra lock locks are required in case of conflicts
            synchronized (lock) {
                synchronized (to) {
                    synchronized (from) {
                        new Helper().transfer();
                    }
                }
            }
        }
    }

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

        int balance;
    }
}
  • If an entity has a primary key, it is more convenient

5.2.2 the dining problem of philosophers

(1) Problem description

  • technological process
① First pick up the left chopsticks
 ② Then he picked up the chopsticks with his right hand
 ③ If chopsticks are used, wait for others to use them
 ④ After eating, put the chopsticks back in place

(2) There are risks of deadlock and resource exhaustion

  • Deadlocks: every philosopher holds his left-handed chopsticks, [always waiting for the right] chopsticks (or vice versa)

(3) Code demonstration: philosophers enter deadlock

/**
 * DiningPhilosophers
 *
 * @author venlenter
 * @Description: Demonstrate the deadlock caused by the dining problem of philosophers
 * @since unknown, 2020-06-14
 */
public class DiningPhilosophers {
    public static class Philosopher implements Runnable {
        private Object leftChopstick;
        private Object rightChopstick;

        public Philosopher(Object leftChopstick, Object rightChopstick) {
            this.leftChopstick = leftChopstick;
            this.rightChopstick = rightChopstick;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    doAction("Thinking");
                    synchronized (leftChopstick) {
                        doAction("Picked up left chopstick");
                        synchronized (rightChopstick) {
                            doAction("Pick 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];
        for (int i = 0; i < chopsticks.length; i++) {
            chopsticks[i] = new Object();
        }
        for (int i = 0; i < philosophers.length; i++) {
            Object leftChopstick = chopsticks[i];
            Object rightChopstick = chopsticks[(i + 1) % philosophers.length];
            philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
            new Thread(philosophers[i], "philosopher" + (i + 1) + "Number").start();

        }
    }
}
//Output results
//Philosopher 1 Thinking
//Philosopher 2 Thinking
//Philosopher 3 Thinking
//Philosopher 4 Thinking
//Philosopher 5 Thinking
//Philosopher No.2 Picked up left chopstick
//Picked up left chopstick
//Philosopher No.5 Picked up left chopstick
//Picked up left chopstick
//Picked up left chopstick
//Program jam, deadlock

(4) Multiple solutions

  • Server check (avoidance strategy): the server checks to see if there is a deadlock. If the check is possible, ask you to stop asking for a meal first
  • Changing the order in which a philosopher holds chopsticks (avoidance strategy)
  • Meal ticket (avoidance strategy): provide meal ticket in advance, only those who get meal ticket can eat
  • Lead adjustment (detection and recovery strategy); let the program execute normally. When A deadlock is found, an external instruction comes in to terminate one of the threads, which is equivalent to breaking the "no deprivation condition" of deadlock (thread A holds lock-a, and the outside world cannot deprive A of lock-a)

(5) Code demonstration: resolving deadlock

  • Changing the order in which a philosopher holds chopsticks (avoidance strategy)
/**
 * DiningPhilosophers
 *
 * @author venlenter
 * @Description: Demonstrate the deadlock caused by the dining problem of philosophers
 * @since unknown, 2020-06-14
 */
public class DiningPhilosophers {
    public static class Philosopher implements Runnable {
        private Object leftChopstick;
        private Object rightChopstick;

        public Philosopher(Object leftChopstick, Object rightChopstick) {
            this.leftChopstick = leftChopstick;
            this.rightChopstick = rightChopstick;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    doAction("Thinking");
                    synchronized (leftChopstick) {
                        doAction("Picked up left chopstick");
                        synchronized (rightChopstick) {
                            doAction("Pick 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];
        for (int i = 0; i < chopsticks.length; i++) {
            chopsticks[i] = new Object();
        }
        for (int i = 0; i < philosophers.length; i++) {
            Object leftChopstick = chopsticks[i];
            Object rightChopstick = chopsticks[(i + 1) % philosophers.length];
            //Improvement: when the last philosopher, on the other hand, take the chopsticks on the right
            if (i == philosophers.length - 1) {
                philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
            } else {
                philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
            }
            new Thread(philosophers[i], "philosopher" + (i + 1) + "Number").start();
        }
    }
}
//Output results
//Printing for a long time without deadlock

5.2.3 deadlock detection and recovery strategy

(1) Detection algorithm: call link graph of lock

  • Allow deadlock
  • Record every lock call
  • Periodically check for loops in the lock call link graph
  • Once deadlock occurs, it can be recovered by deadlock recovery mechanism

(2) Recovery method 1: [process abort]

  • Terminate the threads one by one until the deadlock is removed
  • Termination order
① Priority (foreground interaction or background processing)
② Occupied resources and needed resources (if a little more resources are needed to complete the task, priority will be given to execution and other resources will be terminated)
③ Run time (if the task has been running for a long time and is about to complete, the task will be executed first and the others will be terminated)

(3) Recovery method 2: resource preemption

  • Take back the locks that have been distributed to
  • Let the thread [back off a few steps], so that it does not need to end the whole thread, [the cost is relatively low]
  • Disadvantage: the same thread may be preempted all the time, which will cause hunger

6. How to avoid deadlock in practical engineering

6.1 setting [timeout] time

  • Trylock (long timeout, timeunit) of Lock
  • synchronized does not have the ability to attempt locks
  • There are many possibilities to cause timeout: deadlock occurs, thread gets stuck in a dead cycle, and thread execution is slow
  • When lock acquisition fails: print error log, send alarm email, restart, etc
/**
 * TryLockDeadlock
 *
 * @author venlenter
 * @Description: Try lock to avoid deadlock
 * @since unknown, 2020-06-14
 */
public class TryLockDeadlock implements Runnable {
    int flag = 1;
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    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();
    }

    @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 got lock 1");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                            System.out.println("Thread 1 got lock 2");
                            System.out.println("Thread 1 successfully obtained two locks and released all locks");
                            lock2.unlock();
                            lock1.unlock();
                            break;
                        } else {
                            System.out.println("Thread 1 failed to get lock 2, retried, releasing lock 1");
                            lock1.unlock();
                            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 got lock 2");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock1.tryLock(3000, TimeUnit.MILLISECONDS)) {
                            System.out.println("Thread 2 got lock 1");
                            System.out.println("Thread 2 successfully acquired two locks and released all locks");
                            lock1.unlock();
                            lock2.unlock();
                            break;
                        } else {
                            System.out.println("Thread 2 failed to acquire lock 1, retried, releasing lock 2");
                            lock2.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    } else {
                        System.out.println("Thread 2 failed to acquire lock 2, retried");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
//Output results
//Thread 1 got lock 1
//Thread 2 got lock 2
//Thread 1 failed to get lock 2, retried, releasing lock 1
//Thread 2 got lock 1
//Thread 2 successfully obtained two locks and released all locks
//Thread 1 got lock 1
//Thread 1 got lock 2
//Thread 1 successfully obtained two locks and released all locks

6.2 use concurrent classes more than designing locks by yourself

  • Concurrent HashMap, concurrent linkedqueue, atomic Boolean, etc
  • In practical application java.util.concurrent.atomic is very useful, simple, convenient and more efficient than Lock
  • Multi use [concurrent set] less use of synchronous set( Collections.synchronizedMap() and Collections.synchronizedList()), concurrent collections are more scalable than synchronous collections
  • Concurrent scenarios need to use map. First of all, I want to use concurrent HashMap

6.3 minimize the use of locks [granularity]: use different locks instead of one lock

6.4 if you can use the synchronization code block, you do not need to use the synchronization method: it is convenient to specify the lock object by yourself instead of directly using the entire method

6.5 give the thread a meaningful name: debug and troubleshooting current events with half the effort. The framework and JDK follow this best practice

6.6 avoid lock nesting: MustDeadLock class

synchronized(lock1) {
    synchronized(lock2) {
	    //xxx
	}
}

6.7 before allocating resources, check whether they can be recovered: banker's algorithm

6.8 try not to use the same lock for several functions: [special lock only]

7. Other active faults

  • Deadlock is the most common activity problem, but in addition to the deadlock just now, there are some similar problems that will cause the program to fail to execute smoothly, collectively referred to as activity problems
  • [livelock]
  • Hunger

7.1 live lock

7.1.1 what is a live lock

  • Although the thread is not blocked and [is always running] (so it is called "live" lock, thread is "live"), but the program [can't get progress], because the thread always does the same thing repeatedly (always asking for the other party's lock) (occupying CPU at the same time)
  • If it's a deadlock, then it's blocking and waiting for each other (without taking up CPU)
  • Dead lock and live lock [the result is the same], is waiting for each other

7.1.2 code demonstration

/**
 * LiveLock
 *
 * @author venlenter
 *@ Description: demonstrate the live lock problem
 * @since unknown, 2020-06-15
 */
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 finished!", owner.name);
        }
    }

    static class Diner {
        private String name;
        private boolean isHunger;

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

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHunger) {
                if (spoon.owner != this) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                if (spouse.isHunger) {
                    System.out.println(name + ": dear"+ spouse.name  +"Eat first");
                    spoon.setOwner(spouse);
                    continue;
                }
                spoon.use();
                isHunger = false;
                System.out.println(name + ":" + "I'm finished");
                spoon.setOwner(spouse);
            }
        }
    }

    public static void main(String[] args) {
        Diner husband = new Diner("cowboy");
        Diner wife = new Diner("Weaver Girl");
        Spoon spoon = new Spoon(husband);
        new Thread(() -> husband.eatWith(spoon, wife)).start();
        new Thread(() -> wife.eatWith(spoon, husband)).start();
    }
}
//Output results
 Cowherd: Dear weaver girl, eat first
 Zhinu: Dear cowherd, you can eat first
 Niulang: Dear Zhinv, please eat first
 Zhinu: Dear cowherd, you can eat first
 ... / / output alternately and continuously without stopping
 Niulang: Dear Zhinv, please eat first
 Zhinu: Dear cowherd, you can eat first
 Cowherd: Dear weaver girl, eat first
 Zhinu: Dear cowherd, you can eat first

7.1.3 how to solve the problem of live lock

  • Reason: the retrial mechanism remains unchanged, the message queue is always retrying, [eating always humble]
  • Exponential backoff algorithm of Ethernet: both sides wait at random time and then try again, not because they collide again at the same time
  • Adding random factors
/**
 * LiveLock
 *
 * @author venlenter
 * @Description: Demonstrate live lock problem
 * @since unknown, 2020-06-15
 */
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 isHunger;

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

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHunger) {
                if (spoon.owner != this) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                Random random = new Random();
                //Adding random factors
                if (spouse.isHunger && random.nextInt(10) < 9) {
                    System.out.println(name + " : dear" + spouse.name + "You can eat first");
                    spoon.setOwner(spouse);
                    continue;
                }
                spoon.use();
                isHunger = false;
                System.out.println(name + " : " + "I'm full");
                spoon.setOwner(spouse);
            }
        }
    }

    public static void main(String[] args) {
        Diner husband = new Diner("a cowboy");
        Diner wife = new Diner("woman weaver");
        Spoon spoon = new Spoon(husband);
        new Thread(() -> husband.eatWith(spoon, wife)).start();
        new Thread(() -> wife.eatWith(spoon, husband)).start();
    }
}
//Output results
//Cowherd: Dear weaver girl, eat first
//Zhinu: Dear cowherd, you can eat first
//Niulang: Dear Zhinv, please eat first
//Vega finished! Vega: I finished
//Cowboy finished! Cowboy: I finished

7.1.4 example of live lock in the project: [message queue]

  • Error method: if the message fails to be processed and retried at the beginning of the queue, if there is a problem with the service and the message fails to be processed all the time, the program will be stuck all the time
  • Solution: put the failed message at the end of the queue, and limit the number of retries (for example, limit the number of reconnections for 3 times, and do other logic if more than 3 times)

7.2 hunger

  • When a thread needs some resources (such as CPU), but [never get]
  • The [priority] of a thread is set too low (if set to 1), or a thread holds a lock and has an infinite loop to [do not release the lock], or a program [always occupies] the [write lock] of a file
  • Starvation may lead to poor responsiveness: for example, the browser has thread A responsible for foreground response (opening favorites and other actions), and thread B is responsible for downloading pictures and files in the background, computing and rendering, etc. If background thread B takes up all CPU resources, foreground thread A will not perform well, which will lead to poor user experience

8. Common interview questions

(1) Write an example of "inevitable deadlock". In what scenario will deadlock occur in production?

  • Example: thread set flag to distinguish start, call each other's lock (AB, BA)
  • In what scenarios do deadlocks occur: Lock calls to each other
/**
 * MustDeadLock
 *
 * @author venlenter
 * @Description: Deadlock is bound to occur
 * @since unknown, 2020-06-10
 */
public class MustDeadLock implements Runnable {
    int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

    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();
    }
    @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 gets two locks successfully");
                }
            }
        }
        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");
                }
            }
        }
    }
}

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

  • Mutex condition (if thread A gets lock-a, other threads can only wait if they want to acquire lock-a)
  • Request and hold condition (thread A keeps lock-a lock when it requests lock-b)
  • No deprivation condition (thread A holds lock-a, and the outside world cannot deprive A of lock-a)
  • Loop wait condition (multiple threads form A loop, A waits for B, B waits for C, C waits for A)

3. How to locate deadlock

  • jstack: after the life and death lock is issued, the thread details are output through pid dump
  • ThreadMXBean: detect in code

4. What are the strategies to solve deadlock?

  • Avoidance strategies: the change of hands (the last person switches direction) and the transfer sequence (the order of lock acquisition is determined by hashcode)
  • Detection and recovery strategy: for a period of time [detect] whether there is a deadlock, if there is a deadlock, then [deprive] a resource to open the deadlock
  • Ostrich strategy: not recommended

5. Talk about the classic problem of dining philosophers

  • Solution

6. How to avoid deadlock in practical engineering?

  • ① Set the timeout time
  • ② Use concurrent classes more than designing locks by yourself
  • ③ Minimize the use of locks [granularity]: use different locks instead of one
  • ④ If you can use the synchronization code block, you do not need to use the synchronization method: it is convenient to specify the lock object by yourself instead of directly using the entire method
  • ⑤ Give threads a meaningful name: debug and troubleshooting are half done, and the framework and JDK follow this best practice
  • ⑥ Avoid nesting of locks: MustDeadLock class
  • ⑦ To allocate resources and money, we need to see if we can get it back: banker's algorithm
  • ⑧ Try not to use the same lock for several functions: [special lock only]

7. What is the problem of activity? What's the difference between a live lock, a hungry lock and a dead lock?

 

Note source: moocnet teacher Wukong video Java Concurrent core knowledge system

Topics: Java JDK Database jvm