050.JAVA thread_ Implementation of thread communication and deadlock

Posted by haynest on Tue, 08 Mar 2022 22:43:09 +0100

Github address of the blogger

1. Concept and implementation of thread communication

  • Different threads perform different tasks. If these tasks are related, threads must be able to communicate in order to coordinate and complete the work

1.1. Case interpretation

  • Classic producer / consumer cases:

1.1.1. Analysis diagram

1) Producers and consumers should operate on shared resources 2) Use one or more threads to represent a producer 3) Use one or more threads to represent a consumer 4) This design pattern that separates producer / resource / consumer embodies the idea of object-oriented

1) Low coupling is realized, so that everything is independent of each other; 2) Let producers and consumers exchange data through a third party so that they do not need to reference each other

1.1.2. Case description

  • Get a new and more detailed case description above:

    • Producers are responsible for storing name and gender data
    • The consumer is responsible for taking out the data and printing it
  • For producers:
    If there is no data in the shared resource, the producer is responsible for producing a data (Name Gender), and the shared resource is not empty
    Otherwise, producers wait for consumers to consume the data

  • For consumers:
    If there is no data in production, the producer should share the data
    Otherwise, the consumer takes out the data The shared resource is empty

1.1.3. Case implementation (no synchronization, no thread communication)

  • Implementation of shared resource class

    //Shared resource object (Name Gender) 
    public class ShareResource {
      private String name;
      private String gender;
    
      /**
       * Producers store data into shared resource objects
       * @param name Stored name
       * @param gender Stored gender
       */
      public void push(String name, String gender) {
          this.name = name;
          this.gender = gender;
      }
    
      /**
      * Consumer fetches data from shared resource object
      */
      public void popup() {
          System.out.println(this.name + "-" + this.gender);
      }
    }
  • Implementation of producer class

    //producer
    public class Producer implements Runnable {
      //Shared resource object, set to null
      private ShareResource resource = null;
    
      //Common shared resources are passed in through the constructor 
      public Producer(ShareResource resource) {
          this.resource = resource;
      }
    
      public void run() {
          for (int i = 0; i < 50; i++) {
              if (i % 2 == 0) {
                  resource.push("Brother chun", "male");
              } else {
                  resource.push("Miss Luo Yu feng", "female");
              }
          }
      }
    }
  • Implementation of consumer class

    //consumer
    public class Consumer implements Runnable {
      //Shared resource object
      private ShareResource resource = null;
    
      public Consumer(ShareResource resource) {
          this.resource = resource;
      }
    
      public void run() {
          for (int i = 0; i < 50; i++) {
              resource.popup();
          }
      }
    }
  • Implementation of test class

    //Test code
    pablic class App {
      public static void main (String[] args) {
          //Create a common resource object for producers and consumers
          ShareResource resource = new ShareResource();
          //Start producer thread
          new Thread(new Producer(resource)).start();
          //Start consumer thread
          new Thread(new Consumer(resource)).start();
      }
    }

1.1.4. Implementation effect and exception

  • It will be found that gender disorder occurs, and there is no ideal effect of alternating printing between a man and a woman

  • Then by adding sleep to the storage method of resources, gender disorder will occur

    • Modified code
      public void push(String name, String gender) {
        this.name = name;
        try {
            Thread.sleep(10);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        this.gender = gender;
      }
    • The running results are different from the preset stored results, and even the same results will be printed continuously

1.1.5. Anomaly analysis

  • There are two exceptions in the above operation results:

    • Exception 1:
      There is a disorder of surnames

    • Exception 2:
      There should be one data for production and one data for consumption;
      It should appear alternately: brother chun - male - > Sister Feng - female - > brother chun - male - > Sister Feng - Female

  • Cause of abnormality:

    • The first problem is that the threads are not synchronized with each other, resulting in data acquisition disorder and gender disorder
    • The second problem is that there is no wait and wake mechanism, so there is no alternate execution

1.1.6. Exception resolution

  • The solution to exception 1 is:
    We only need to ensure that the process of producing name and gender is synchronized, and the consumer thread cannot interfere in the process
    You can use synchronous code block / synchronous method / locking mechanism to solve this problem

      //Shared resource object (Name Gender) 
      public class ShareResource {
          private String name;
          private String gender;
    
          /**
          * Producers store data into shared resource objects
          * @param name Stored name
          * @param gender Stored gender
          */
          synchronized public void push(String name, String gender) {
              try {
                  this.name = name;
                  Thread.sleep(10);
                  this.gender = gender;
              } catch(Exception ex) {
                  ex.printStackTrace();
              }
          }
    
          /**
          * Consumer fetches data from shared resource object
          */
          synchronized public void popup() {
              try {
                  Thread.sleep(10);
                  System.out.println(this.name + "-" + this.gender);
              } catch(Exception ex) {
                  ex.printStackTrace();
              }
          }
      }

2. Use the wait and notify methods in the Object class to realize thread communication

2.1. Thread communication methods commonly used in object class

java.lang.Object class provides two methods for operating thread communication

  • wait() method:
    The thread calling the object method will release its own synchronization lock, and then the JVM will save it into the waiting pool,
    The thread starts waiting for other threads to wake it up It cannot wake itself up, but can only be awakened by other threads

  • notify() method:
    The thread calling the object method will wake up any thread waiting in the waiting pool,
    Then the thread is transferred to the lock pool to wait

  • notifyAll() method:
    The thread calling the object method will wake up all the threads waiting in the waiting pool and turn the thread to the lock pool to wait

  • be careful:
    The above method can only be called by the synchronous listening lock object, otherwise the error IllegalMonitorStateException will be reported

2.2. Communication process

Suppose that thread A and thread B jointly operate an X object and set the X object as A synchronization lock,
Therefore, a and B threads can communicate through the wait and notify methods of X object,
The process is as follows:

  • When thread A executes the synchronization method of X object, thread A holds the lock of X object,
    At this time, thread B has no chance to execute, so thread B waits in the lock pool of X object

  • When thread A executes the X.wait() method in the synchronization method, thread A releases the lock of the X object,
    At this time, thread A will enter the waiting pool of X object and wait to be awakened

  • The B thread waiting for the lock in the lock pool of the X object releases the lock after the A thread releases the lock,
    The lock of X object will be acquired and another synchronization method of X will be executed

  • When the B thread executes the X.notify() method in the synchronization method, the JVM removes the A thread from the X object
    Move to the lock pool of X object and wait to acquire the lock

  • After thread B executes the synchronization method, it will release the lock of X object, so thread A obtains the lock and continues to execute the synchronization method

2.3. Using wait and notify methods to realize thread communication of consumer cases

  • Implementation of shared resource class

    //Shared resource object (Name Gender) 
    public class ShareResource {
      private String name;
      private String gender;
    
      //Indicates whether the shared resource object is empty
      private Boolean isEmpty = true;
    
      /**
      * Producers store data into shared resource objects
      * @param name Stored name
      * @param gender Stored gender
      */
      synchronized public void push(String name, String gender) {
    
          try {
              while(!isEmpty){
              //When the shared resources are not empty, we should wait for consumers to consume before production
              //At this point, the thread calling the method should enter the waiting state
              this.wait();
              }
    
              //-----Start of production------
              this.name = name;
              //Execution wait interval simulation delay
              Thread.sleep(10);
              this.gender = gender;
              //-----End of production------
              //After production, the shared resource object becomes non empty
              isEmpty = false;
              //Wake up the consumer thread to avoid blocking
              this.notify();
          } catch (Exception ex) {
              ex.printStackTrace();
          }
      }
    
      /**
      * Consumer fetches data from shared resource object
      */
      synchronized public void popup() {
    
          try {
              while(isEmpty){
              //When the shared resources are empty, we should wait for the producer to produce before consumption
              //At this point, the thread calling the method should enter the waiting state
              this.wait();
              }
    
              //-----Consumption starts------
              System.out.println(this.name + "-" + this.gender);
              //-----End of consumption------
              //After consumption, the shared resource object becomes empty
              isEmpty = true;
              //Wake up the producer thread to avoid entering the blocking state
              this.notify();
          } catch (Exception ex) {
              ex.printStackTrace();
          }
    
      }
    }
  • The implementation of producer class / consumer class / test class is consistent with the previous code and remains unchanged

  • Execution effect:
    Obviously, the output results are produced and consumed alternately, and the data interaction between threads is not disordered

3. Lock mechanism and Condition interface realize thread communication

3.1. Thread communication requirements of lock mechanism

  • There is no synchronous Lock in the Lock mechanism, so there is no concept of automatically acquiring and releasing locks

  • At the same time, because there is no synchronization Lock, the wait() and notify() methods cannot be called in the Lock mechanism

  • To solve this problem, we need to use the Condition interface, which can be used to handle the communication control of Lock mechanism

3.1.1. The lock interface provides a method to obtain a Condition instance

  • newCondition() method:
    This method is provided by the Lock interface to return a new Condition instance bound to this Lock instance

3.2. Introduction to condition interface

  • The Condition interface will monitor methods in the Object class (including wait(), notify())
    And notifyAll()) are decomposed into distinct objects

  • In this way, these Condition objects can be combined with any Lock object,
    Provide multiple wait sets for each Lock object

  • Lock mechanism replaces synchronized methods and statements,
    The Object method of the Condition interface replaces the monitor method of the Object in the Object class

3.2.1. Method in condition interface

  • await() method:
    Causes the current thread to wait until it receives a signal or is interrupted

  • signal() method:
    Wake up a waiting thread

  • signalAll() method:
    Wake up all waiting threads

3.2.2. The thread lock mechanism is used to realize the communication of consumers

  • be careful:
    When entering the method, you must obtain the lock immediately, otherwise two exceptions will occur:
    • To operate on a Condition object, you must first have a lock object
    • To prevent other threads from entering the method together, the lock must be obtained first
//Shared resource object (Name Gender) 
public class ShareResource {
    private String name;
    private String gender;

    //Indicates whether the shared resource object is empty
    private Boolean isEmpty = true;

    //Define a lock object
    private final Lock lock = new ReentrantLock();
    //Define a Condition interface object
    private Condition condition = lock.newCondition();

    /**
    * Producers store data into shared resource objects
    * @param name Stored name
    * @param gender Stored gender
    */
    public void push(String name, String gender) {
        //Obtain the lock immediately after entering the method
        lock.lock();

        try {
            while (!isEmpty){
                //Wait for consumers to consume when it is not empty
                condition.await();
            }
            this.name = name;
            Thread.sleep(10);
            this.gender = gender;
            //Wake up other threads and change the state of resource object after production
            condition.signalAll();
            isEmpty = false;
        } catch(Exception ex) {
            ex.printStackTrace();
        } finally {
            //Release the lock after execution
            lock.unlock();
        }
    }

    /**
    * Consumer fetches data from shared resource object
    */
    public void popup() {
        //Obtain the lock immediately after entering the method
        lock.lock();

        try {
            while (isEmpty){
                //When empty, wait for the producer to produce
                condition.await();
            }
            Thread.sleep(10);
            System.out.println(this.name + "-" + this.gender);
            //After consumption, wake up other threads and change the state of resource objects
            condition.signalAll();
            isEmpty = true;
        } catch(Exception ex) {
            ex.printStackTrace();
        } finally {
            //Release the lock after execution
            lock.unlock();
        }
    }
}

4. Deadlock in thread communication

  • It is easy to cause deadlock in multithreaded communication. Deadlock cannot be solved and can only be avoided

4.1. Deadlock generation

  • When thread A waits for A lock held by thread B while thread B is waiting for A lock held by thread A,
    Deadlock will occur, and the JVM will not detect or avoid deadlock, so it must be avoided when writing

4.2. The law of avoiding deadlock

  • When multiple threads want to access the shared resource A/B/C, ensure that each thread follows the
    Access them in the same order. For example, access A first, then B, and finally C

4.3. Obsolete methods in thread class

  • suspend() method:
    Make the running thread abandon the CPU and suspend the running

  • resume() method:
    Resume the suspended thread

  • Reasons for obsolescence:
    Because it is easy to cause deadlock, it is discarded

  • Deadlock reason:
    Suppose thread A obtains the object lock and executes A synchronization method at the same time,
    At this time, thread B calls thread A's suspend() method to suspend thread A,
    Therefore, thread A will pause and abandon the CPU, but thread A will not release the synchronization lock
    As a result, it causes deadlock, which makes thread B unable to obtain synchronization lock in subsequent execution