Solve the dining problem of philosophers

Keywords: Java Multithreading

1. What is the dining problem of philosophers

If there are five philosophers, they sit at the same table and eat at the same time. After eating, they think. Each person has a chopstick on his left and right, with a total of five chopsticks
So how can these five people eat at the same time?
1. Question conversion: philosophers are equivalent to five threads competing for five resources at the same time (five chopsticks are equivalent to five resources). So how to make the five threads use their own resources without life and death lock?
2. What is deadlock:
Deadlock refers to a blocking phenomenon caused by two or more processes competing for resources or communicating with each other in the execution process. If there is no external force, they will not be able to advance. At this time, the system is in a deadlock state or the system has a deadlock. These processes that are always waiting for each other are called deadlock processes
The same applies to threads
3. Four necessary conditions for deadlock generation:
      1. Mutual exclusion (a resource can only be used by one thread at a time)
      2. Request and hold (a thread is requesting a resource and will not let go of the originally occupied resource). This means that it does not release the resource itself
      3. Inalienable: during thread operation, the acquired resources cannot be forcibly deprived before they are used up. This means that other threads cannot forcibly occupy the resources they have obtained
      4. Circular waiting condition: a circular waiting resource relationship is formed between several threads.
      5. If one of the above conditions is not met, it will not cause deadlock

2. Give an example of deadlock in life


3. Take an example of deadlock in Java code

@Slf4j
public class MyTest {
    private static Object resource01 = new Object();
    private static Object resource02 = new Object();
    //Because the code is demo code, the thread pool is not used to get threads
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource01) {
                log.info("Use resource 1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource02) {
                    log.info("Using resources 2");
                }
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (resource02) {
                log.info("Using resources 2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource01) {
                    log.info("Use resource 1");
                }
            }
        }, "t2").start();
    }
}

4. First design the philosopher dining problem and implement it by thread

analysis:
1. According to object-oriented thinking, we regard philosophers as a class and chopsticks as a class,
Because philosophers use chopsticks, philosophers are threads, so they should inherit thread, and philosophers use chopsticks, and philosophers can't eat without chopsticks, so the relationship between philosophers and chopsticks is associative, that is, chopsticks are an instance variable in philosophers

1. Philosophers:

@Slf4j
public class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;
    String name;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
        this.name = name;

    }

    @Override
    public void run() {
        while (true) {
            //Try to get left chopsticks
            log.debug("Waiting for chopsticks:" + left.name);
            synchronized (left) {
                System.out.println(name + "Get chopsticks:" + left.name);
                //Try to get right-hand chopsticks
                log.debug("Waiting for chopsticks:" + right.name);
                synchronized (right) {
                    eat(name);
                }
            }
        }
    }

    private void eat(String name) {
        log.debug("eating...");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug(name + "I'm full...");
    }
}

2. Chopsticks:

class Chopstick {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "chopsticks{" + name + "}";
    }
}

3. Test:

class TestDeadLock {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        /**
         *  1 2
         *  2 3
         *  3 4
         *  4 5
         *  1 5
         *
         *  1 -->Wait 2 2 -- > wait 3 3 -- > wait 4 -- > wait 5 5 do not execute
         *
         */
        new Philosopher("Socrates", c1, c2).start();
        new Philosopher("Plato", c2, c3).start();
        new Philosopher("Aristotle", c3, c4).start();
        new Philosopher("Heraclitus", c4, c5).start();
        new Philosopher("Archimedes", c5, c1).start();


    }
}

Analysis: a deadlock will occur at this time. As we see, because synchronizedc1 is added, it meets the following four sufficient conditions: mutual exclusion, request and hold, and it is inalienable. In addition, because c1 is waiting for C2, C2 is waiting for C3, C3 is waiting for c4c4 is waiting for C5, and C5 is waiting for c1 from the code, it meets the four sufficient conditions of deadlock

5. Analyze and solve the deadlock problem of philosophers' dining

Analysis 1:
       1. As long as any of these four conditions is not met, there will be no deadlock problem
       2. Let's think about the simplest way. From the code, we can see that c1 is waiting for C2, C2 is waiting for C3, C3 is waiting, c4c4 is waiting for c5,c5 is waiting
         Wait c1. So we just need to break the circular waiting conditions and add restrictions to their meals. For example, we don't let Archimedes take the fifth chopstick first. I          We also let it take the first chopstick first, and then break the condition of circular waiting. There will be no deadlock

  Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        /**
         *  1 2
         *  2 3
         *  3 4
         *  4 5
         *  1 5
         *
         *  1 -->Wait 2 2 -- > wait 3 3 -- > wait 4 -- > wait 5 5 do not wait
         *
         */
        new Philosopher("Socrates", c1, c2).start();
        new Philosopher("Plato", c2, c3).start();
        new Philosopher("Aristotle", c3, c4).start();
        new Philosopher("Heraclitus", c4, c5).start();
        new Philosopher("Archimedes", c1, c5).start();

Analysis 2:
First of all, we should analyze whether we can break the ring mutual exclusion condition. If we break the mutual exclusion condition, there will be instruction interleaving in the process of multi-threaded access, resulting in thread insecurity. Therefore, we can eliminate this first
Secondly, can we destroy the inalienable and the conditions of request and maintenance? The support of these two conditions is due to the synchronized lock,
The synchronized lock ensures that resources cannot be released and can only be occupied before execution is completed. Because synchronized is the underlying implementation of the jvm, we can't make an article on synchronized. Is there a lock that can replace synchronized and release resources when it is not satisfied, and can ensure mutual exclusion conditions? Of course, there is ReentrantLock. Let's use ReentrantLock to solve this problem

@Slf4j
class Philosophers extends Thread {
    Chopsticks left;
    Chopsticks right;

    public Philosophers(String name, Chopsticks left, Chopsticks right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            /* synchronized (left) {
                 synchronized (right) {
                     eat();
                 }
             }*/
            if (left.tryLock()) {
                try {
                    if (right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }

    private void eat() {
        log.debug("having dinner....");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("I'm full...");
    }
}

class Chopsticks extends ReentrantLock {
    String name;

    public Chopsticks(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopsticks{" +
                "name='" + name + '\'' +
                '}';
    }
}

Posted by joe_C_nice on Mon, 20 Sep 2021 15:04:26 -0700