Basic understanding of java multithreading

Posted by Pryach on Thu, 20 Jan 2022 13:03:58 +0100

Reference blog: https://angela.blog.csdn.net/article/details/105625514

1. Processes and threads

An application is a process. A process can contain multiple threads. From the operating system level, threads in the same process share the resources of the process, such as memory space and file handle. Threads are lightweight processes in Linux operating system.

2. There are two ways to create threads

  • Inherit Thread class;
  • Implement Runnable interface;

The example code is as follows:

public static void main(String[] args) {
  new Seller("pen").start();
  new Thread(new Seller02("book")).start();
}

//The first way is to inherit Thread
public static class Seller extends Thread{

  String product;

  public Seller(String product){
    this.product = product;
  }

  @Override
  public void run() {
    System.out.println("inherit Thread Class sale " + product);
  }
}

//The second way is to implement Runnable
public static class Seller02 implements Runnable{

  String product;

  public Seller02(String product){
    this.product = product;
  }

  @Override
  public void run() {
    System.out.println("realization Runnable Interface selling " + product);
  }
}

2.1. Differences between start() and run() methods

Object calling run() method is a local Thread operation; Call the start() method, which is the method given by the Thread class in java to operate the threads in the current operating system.

When the start() method is executed, the JVM will start another thread to execute the code in the run() method.

When we create a thread in java code, it is as follows:

 new Seller("pen")

The operating system won't really help us create a thread out when we call ' After the start() 'method, java maps the threads of the operating system through the interface with the operating system.

In fact, threads in java are the mapping of threads in the operating system. Threads written in java code are mapped to threads in the operating system.

Example description:

public static void main(String[] args) {
  new Seller("pen").run(); //No other thread started
  new Seller("pen").start(); //Execute the run function in the new thread
}

//The first way is to inherit Thread
public static class Seller extends Thread{

  String product;

  public Seller(String product){
    this.product = product;
  }

  @Override
  public void run() {
    System.out.println(String.format("Current thread: %s sell%s", Thread.currentThread().getName(), product));
  }
}

View console results:

Current thread: main Sell pen
 Current thread: Thread-1 Sell pen

You can see that the current thread name is, and the second is the new thread object. Because after calling the start() method, the JVM will create a new thread to execute the contents of the run() method.

2.2. Thread features

The newly created Thread object in Java is only the carrier of the operating system Thread. The Thread class has two main functions:

1. The attributes in the Thread object provide the Thread description information required to create a new Thread, such as Thread name, Thread id, Thread group, and whether it is a daemon Thread;
2. The methods in the Thread object provide the means for Java programs to deal with operating system threads, such as wait, sleep, join, interrupt, etc.
When we talked about the JVM new Thread object earlier, we didn't actually create a thread. The formal creation began when we called the start() method.

The creation and destruction of threads need to be explained from the thread life cycle.

3. Thread life cycle

In java threads, an enumeration class is provided to describe the status of threads:

public static enum State {
  NEW,
  RUNNABLE,
  BLOCKED,
  WAITING,
  TIMED_WAITING,
  TERMINATED;
}

The following figure is used to illustrate:

The above indicates the status of threads, and also explains how threads can switch from one state to another through methods.

3.1 thread status description

  • New: This is the state of the thread just created but not started. Since a thread can only be started once, a thread can only be in this state once.

  • Runnable: as shown in the figure above, this state is actually a composite state, including two sub states: ready and running. Ready is the ready state and can be scheduled by the JVM Thread scheduler. If it is a single core CPU, only one Thread is in the running state at the same time, and multiple threads may be in the ready state. Running indicates that the current Thread is being executed by the CPU. In Java, only the run() method of Thread object is being executed. When the yield() method is called or the Thread time slice is used up, the Thread will change from running state to ready state.

  • Blocked: a thread will enter this state when a blocked I/0 (file read / write I/O, network read / write I/O) occurs, or when it attempts to obtain a lock held by another thread, for example, to obtain a synchronized modified object lock already held by another thread. Threads in the blocked state will not occupy CPU resources (after reading a lot of information here, it is finally clear here). However, if a large number of threads in this state appear in the program, you need to be vigilant. You can consider optimizing the program performance.

  • Waiting: a thread executed object wait( ), Thread.join( ) ,LockSupport. After park(), it will enter this state. This state is in an infinite waiting state. There is no specified waiting time. It can be compared with Timed_Waiting comparison, Timed_Waiting has waiting time. If a thread in this state wants to return to the Runnable state, it needs to call object through another thread notify( ),Object.notifyAll( ),LockSupport.unpark( thread ).

  • Timed_Waiting: waiting with time limit.

  • Terminated: the Thread whose execution has ended is in this state. The end of the execution of the run() method of the Thread or the early termination due to an exception will put the Thread in this state.

3.2 description of common methods

What do wait(), sleep(), join(), yield(), notify(), and notifyAll() do? What's the difference?

The above methods are thread control methods. JAVA interacts with the operating system threads it creates through these methods, as follows:

  • wait():

The thread waits. Calling this method will make the thread enter the Waiting state. At the same time, it is very important that the thread will release the object lock. Therefore, the wait method is generally used in the synchronization method or synchronization code block;

  • sleep(long time):

Thread sleeps. Calling this method will make the thread enter time_ In the waiting state, a parameter needs to be passed in to call the sleep method to identify the time when the thread needs to sleep;

  • yield:

Thread concession, yield will make the current thread give up the CPU execution time slice and compete with other threads for the CPU time slice again. Generally speaking, threads with high priority are more likely to successfully compete for the CPU time slice, but it is not absolute. Some systems are not sensitive to priority.

  • join:

Calling the join method of another thread in the current thread, the current thread is turned into a blocking state, until the execution of the other thread ends, the current thread will change from blocking state to ready state and wait for CPU to schedule. (I only knew how to use it in this way before, but I have always been able to understand it clearly)

Write a code to understand:

public static void main(String[] args) {
  System.out.println(String.format("Main thread%s Start running...", Thread.currentThread().getName()));
  Thread threadA = new Thread(new ThreadA());
  threadA.start();
  try {
    // The main thread wait(0) releases the thread object lock, and the main thread enters the waiting state
    threadA.join();
  } catch (InterruptedException e) {
    e.printStackTrace();
  }

  System.out.println(String.format("Main thread%s End of operation...", Thread.currentThread().getName()));
}

private static class ThreadA implements Runnable{

  @Override
  public void run() {
    System.out.println(String.format("Child thread%s Start running...", Thread.currentThread().getName()));

    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(String.format("Child thread%s Ready to end operation...", Thread.currentThread().getName()));
  }
}

Console output results:

Main thread main Start running...
Child thread Thread-0 Start running...
Child thread Thread-0 Ready to end operation...
Main thread main End of operation...

The main thread calls threada Join() causes the main thread to wait for the Thread-0 thread to finish executing before continuing executing.

The internal implementation of the join() function is as follows:

public final void join() throws InterruptedException {
  join(0);
}

public final synchronized void join(long millis)
  throws InterruptedException {
  long base = System.currentTimeMillis();
  long now = 0;

  if (millis < 0) {
    throw new IllegalArgumentException("timeout value is negative");
  }

  if (millis == 0) {
    // If the Thread associated with the current Thread object is still alive, the currently executing Thread enters the Waitting state. If the execution of the Thread associated with the current Thread object ends, it will / / call notifyAll() to wake up the Thread entering the Waitting state.
    while (isAlive()) {
      wait(0);
    }
  } else {
    while (isAlive()) {
      long delay = millis - now;
      if (delay <= 0) {
        break;
      }
      wait(delay);
      now = System.currentTimeMillis() - base;
    }
  }
}

//wait belongs to the Object object method
public class Object{
  //Thread entry Time_Watting or Waiting status
  public final native void wait(long timeout) throws InterruptedException;
}

The notes above are very good! I didn't notice this before.

In order to help you understand, I drew a picture (a word does not conform to the above), we compare the code and diagram, the above code has two threads, the main thread and the ThreadA thread, the main thread creates ThreadA and starts the ThreadA thread, then calls threadA.join() will cause the main thread to block until the execution of threada thread ends, isActive becomes false, and the main thread resumes execution.

  • interrupt():

Thread interrupt: calling interrupt method to interrupt a thread is to give the thread a notification signal, which will change an interrupt identification bit inside the thread. The thread itself will not change its state (such as blocking, termination, etc.) due to interrupt. There are two situations when the interrupt method is called:

If the current thread is in the Running state, interrupt() will only change the interrupt flag bit and will not really interrupt the Running thread;
If the thread is currently Timed_Waiting state, interrupt() will make the thread throw InterruptedException.
Therefore, when writing multithreaded programs, we need to handle these two situations at the same time. The conventional writing method is:

public static class ThreadInterrupt implements Runnable{

  @Override
  public void run() {
    //1. In the non blocking state, exit by checking the interrupt identification bit
    while(!Thread.currentThread().isInterrupted()){
      try{
        //doSomething()
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        //2. In the blocking state, the interrupt exception is captured and the break exits
        e.printStackTrace();
        break;
      }
    }
  }
}

This just provides an elegant way to interrupt threads

  • notify():

Like the wait method, the notify method is also a method in the Object class. The notify method is used to wake up a single thread waiting on the Object monitor. If there are multiple threads waiting on the Object monitor, select one of them to wake up. In addition, it should be noted that the lock will not be released immediately after the current thread wakes up the waiting thread, but only after the execution of the current thread is completed. Therefore, the awakened thread can not start execution immediately after waking up, but wait until the execution of the awakened thread is completed and the Object lock is obtained.

Look at the code:

public static void main(String[] args) {
  new Thread(new ThreadA()).start();
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  new Thread(new ThreadB()).start();
}

private static final Object lock = new Object();

private static class ThreadA implements Runnable{
  @Override
  public void run() {
    synchronized (lock){
      System.out.println("Thread-A get into the state running...");

      try {
        System.out.println("Thread-A get into the state waiting...");
        lock.wait();

        System.out.println("Thread-A get into the state running...");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("Thread-A completion of enforcement, get into the state terminated...");
  }
}

private static class ThreadB implements Runnable{

  @Override
  public void run() {
    synchronized (lock){
      System.out.println("Thread-B get into the state running...");
      try {
        System.out.println("Thread-B get into the state time_waiting...");
        Thread.sleep(3000);

        System.out.println("Thread-B get into the state running...");

        lock.notify();
        System.out.println("Thread-B get into the state time_waiting...");
        Thread.sleep(5000);
        System.out.println("Thread-B get into the state running...");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("Thread-B completion of enforcement, get into the state terminated...");
  }
}

View console output information:

Thread-A get into the state running...
Thread-A get into the state waiting...
Thread-B get into the state running...
Thread-B get into the state time_waiting...
Thread-B get into the state running...
Thread-B get into the state time_waiting...
Thread-B get into the state running...
Thread-B completion of enforcement, get into the state terminated...
Thread-A get into the state running...
Thread-A completion of enforcement, get into the state terminated...

You can see that the B thread calls lock After notify(), thread A does not start execution immediately, but waits until thread B finishes execution, so lock When notify() wakes up thread A, it only makes thread A enter the state of ready execution, rather than directly entering the Running state. Thread B calls notify without releasing the object lock immediately.

4. Interview article

4.1 what are the ways to close threads? Which is the best way? (meituan interview question)

1. Use exit flag bit;

public class ThreadSafe extends Thread { 
  public volatile boolean exit = false;
  public void run() { 
    while (!exit){
  		//do something 
    }
  }
}

2. Call the interrupt method, which is the most desirable, but consider handling the two cases;

3. Stop method, which belongs to forced termination, is very dangerous. It's like powering down the thread directly and calling thread When using the stop () method, all locks held by the child thread will be released. This sudden release may lead to data inconsistency. Therefore, this method is not recommended to terminate the thread.

4.2 what is the difference between wait and sleep? (heart to heart interview question)

There are three main points:

1. The sleep method lets the thread enter timed_ In the Waiting state, the sleep method must pass in the time parameter, which will suspend the current thread for a period of time, after which it will return to the runnable state (depending on the accuracy and accuracy of the system timer and scheduler). The wait method will make the current thread enter the Waiting state and block until other threads call the notify or notifyAll method to wake up.

2. Wait is a method in the Object class and sleep is a method in the Thread class. It is important to understand this. The wait method is bound to an Object. Calling the wait method will release the Object lock associated with the wait;

3. If the current thread holds the lock in the synchronization code block, the object lock will be released when the wait method is executed. Sleep is just a simple sleep and will not release the lock;

Let's look at a code to consolidate:

public static void main(String[] args) {
  new Thread(new ThreadA()).start();
  try {
    Thread.sleep(1000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  new Thread(new ThreadB()).start();
}

private static final Object lock = new Object();

private static class ThreadA implements Runnable{
  @Override
  public void run() {
    synchronized (lock){
      System.out.println("Thread-A get into the state running...");

      try {
        System.out.println("Thread-A get into the state waiting...");
        lock.wait();

        System.out.println("Thread-A get into the state running...");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("Thread-A completion of enforcement, get into the state terminated...");
  }
}

private static class ThreadB implements Runnable{

  @Override
  public void run() {
    synchronized (lock){
      System.out.println("Thread-B get into the state running...");
      try {
        System.out.println("Thread-B get into the state time_waiting...");
        Thread.sleep(3000);

        System.out.println("Thread-B get into the state running...");

        lock.notify();
        System.out.println("Thread-B get into the state time_waiting...");
        Thread.sleep(5000);
        System.out.println("Thread-B get into the state running...");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("Thread-B completion of enforcement, get into the state terminated...");
  }
}

The output console control is as follows:

Thread-A get into the state running...
Thread-A get into the state waiting...
Thread-B get into the state running...
Thread-B get into the state time_waiting...
Thread-B get into the state running...
Thread-B get into the state time_waiting...
Thread-B get into the state running...
Thread-B completion of enforcement, get into the state terminated...
Thread-A get into the state running...
Thread-A completion of enforcement, get into the state terminated...

4.3. Handwriting an example of deadlock (meituan two-sided interview question)

private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public static class DeadLockSample implements Runnable{
  Object[] locks;

  public DeadLockSample(Object lock1, Object lock2){
    locks = new Object[2];
    locks[0] = lock1;
    locks[1] = lock2;
  }

  @Override
  public void run() {
    synchronized (locks[0]) {
      try {
        Thread.sleep(3000);
        synchronized (locks[1]) {
          System.out.println(String.format("%s come in...", Thread.currentThread().getName()));
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

public static void main(String[] args) {
  Thread a = new Thread(new DeadLockSample(lock1, lock2));
  Thread b = new Thread(new DeadLockSample(lock2, lock1));

  a.start();
  b.start();
}

Check the console and find that it is stuck all the time.

4.4. Producer consumer code communicating through thread wait / notify? (interview questions on four sides of the sound network)

static class MangoIce{
        int counter;

        public MangoIce(int counter) {
            this.counter = counter;
        }
    }

    static class Producer implements Runnable
    {
        private final List<MangoIce> barCounter;
        private final int           MAX_CAPACITY;

        public Producer(List<MangoIce> sharedQueue, int size)
        {
            this.barCounter = sharedQueue;
            this.MAX_CAPACITY = size;
        }

        @Override
        public void run()
        {
            int counter = 1;
            while (!Thread.currentThread().isInterrupted())
            {
                try
                {
                    produce(counter++);
                }
                catch (InterruptedException ex)
                {
                    ex.printStackTrace();
                    break;
                }
            }
        }

        private void produce(int i) throws InterruptedException
        {
            synchronized (barCounter)
            {
                while (barCounter.size() == MAX_CAPACITY)
                {
                    System.out.println("The bar is full,The smoothie won't fit " + Thread.currentThread().getName() + " Thread waiting,Number of smoothies at the current bar: " + barCounter.size());
                    barCounter.wait();
                }

                Thread.sleep(1000);
                barCounter.add(new MangoIce(i));
                System.out.println("Production section: " + i + "Glass of Smoothie...");
                barCounter.notifyAll();
            }
        }
    }

    static class Consumer implements Runnable
    {
        private final List<MangoIce> barCounter;

        public Consumer(List<MangoIce> sharedQueue)
        {
            this.barCounter = sharedQueue;
        }

        @Override
        public void run()
        {
            while (!Thread.currentThread().isInterrupted())
            {
                try
                {
                    consume();
                } catch (InterruptedException ex)
                {
                    ex.printStackTrace();
                    break;
                }
            }
        }

        private void consume() throws InterruptedException
        {
            synchronized (barCounter)
            {
                while (barCounter.isEmpty())
                {
                    System.out.println("The bar is empty,No smoothies " + Thread.currentThread().getName() + " Consumer thread wait,Number of smoothies at the current bar: " + barCounter.size());
                    barCounter.wait();
                }
                Thread.sleep(1000);
                MangoIce i = barCounter.remove(0);
                System.out.println("Consumption section: " + i.counter + "Glass Smoothie...");
                barCounter.notifyAll();
            }
        }
    }

    public static void main(String[] args)
    {
        List<MangoIce> taskQueue = new ArrayList<>();
        int MAX_CAPACITY = 5;
        Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "producer");
        Thread tConsumer = new Thread(new Consumer(taskQueue), "consumer");
        tProducer.start();
        tConsumer.start();
    }

In other words, multiple threads can coordinate to do things.

Topics: Multithreading