Thread life cycle

Operating system Level

The thread life cycle has five states at the operating system level: initialization state, runnable state, running state, sleep state, and termination state. As shown below:

  • Initialization state: The thread has been created but is not yet allowed to allocate CPU for execution. This state is specific to the programming language, that is, the creation here is only created at the programming language level, but at the operating system level, the actual thread has not been created.
  • Runnable: The thread is ready to allocate CPU for execution. The actual operating system thread has been successfully created, so CPU resources can be allocated.
  • Running state: When an idle CPU is available, the operating system allocates it to a runnable thread. At this point, the state of the thread allocated to the CPU is changed from runnable to running.
  • Dormant: If a running thread calls a blocking API (such as blocking to read a file) or waits for an event (such as a condition variable), the thread transitions to dormant and releases CPU usage. The dormant thread never gains CPU usage. It is only when the waiting event occurs that the thread transitions from sleep to runnable state and gains CPU usage.
  • Termination state: the thread will enter the termination state upon completion of execution or abnormal exit. The thread in the termination state cannot switch to any other state. Entering the termination state means that the life cycle of the thread is over.

Java level

There are six states in the life cycle of a Thread in the Java language. They are NEW(initial state), RUNNABLE(runable state), BLOCKED(TERMINATED), WAITING(untimed wait state), TIMED_WAITING(TERMINATED), and TERMINATED(TERMINATED).

At the operating system level, BLOCKED, WAITING, and TIMED_WAITING in Java threads are all dormant states, meaning that as long as the Java thread is in one of these three states, it does not have access to CUP.

Java thread switching between the six states can be seen in the following figure:

Java thread implementation in several ways

Threads are implemented in Java in the following ways:

Using or inheriting the Thread class:

// Create a thread object
Thread t = new Thread() { 
    public void run(a) { 
    // The task to be performed}};// Start the thread
t.start();
Copy the code

Implement the Runnable interface with the Thread class:

// Separate threads from tasks (code to execute -Runnable)
Runnable runnable = new Runnable() { 
    public void run(a){ 
    // The task to be performed}};// Create a thread object
Thread t = new Thread( runnable ); 
// Start the thread
t.start();
Copy the code

Implement Callable interface with return value

class CallableTask implements Callable<Integer{     
    @Override     
    public Integer call(a) throws Exception {         
        return newRandom().nextInt(); }}// Use FutureTask to create thread objects
Callable<Integer> callableTask = new CallableTask();
FutureTask<String> oneTask = new FutureTask<String>(callableTask);
Thread t = new Thread(oneTask);
// Start the thread
t.start();
Copy the code

Using lambda expressions

new Thread(() -> 
    // Thread task code
).start();
Copy the code

Note: There is essentially only one way to implement a Java Thread, both by creating a Thread with New Thread() and starting it with a call to Thread#start

Java thread implementation principle

Java threads are kernel level threads

Java threads were implemented after JDK1.2 – based on the operating system native thread model. Both the Windows and Linux versions of the Sun JDK are implemented using a one-to-one threading model, where a Java thread is mapped into a lightweight process.

  • Kernel-level threads (KLT) : They are kernel-dependent, that is, they are created, undone, and switched by the Kernel, whether they are threads in user processes or threads in system processes.
  • User Level Threads (ULT) : The operating system kernel is unaware of the existence of application threads.

Currently, Java does not officially support coroutines, but can rely on third-party frameworks such as Kilim/Quasar to implement them

A coroutine is a thread-based, but more lightweight existence. It is not managed by the operating system kernel, but completely controlled by programs (that is, executed in user mode), with features that are not visible to the kernel. The benefit of this is that the performance is greatly improved and the resources are not consumed like thread switching.

Subroutines, or functions, are called hierarchically in all languages, such as when A calls B, when B calls C, when C returns, when B returns, and finally when A finishes. Coroutines are called differently from subroutines. The coroutine is interruptible inside the subroutine and then switches to another subroutine, returning to resume execution at an appropriate time. The following pseudocode, if executed by A coroutine, could interrupt at any time to execute B while executing A, and B could interrupt at any time to execute A, and the final output might be: 12xy3x instead of 123xyz

def A(): 
    print '1' 
    print '2' 
    print '3' 
def B(): 
    print 'x'
    print 'y'
    print 'z'
    
A()
B()
Copy the code

Advantages of coroutines

Coroutines have the following advantages over multithreading:

  • Thread switching is scheduled by the operating system, and coroutines are scheduled by users themselves, thus reducing context switching and improving efficiency.
  • The default stack size for threads is 1M, while coroutines are much lighter, approaching 1K. Therefore, more coroutines can be turned on in the same memory.
  • Do not need multithreaded locking mechanism: because there is only one thread, there is no conflict of variables written at the same time, in the coroutine control of shared resources do not lock, only need to judge the state, so the execution efficiency is much higher than multithreading.

Note: Coroutines are suitable for blocked scenarios that require a lot of concurrency (network IO). Not suitable for heavy computing scenarios.

Java thread scheduling mechanism

Thread scheduling refers to the process in which the system allocates CPU processor rights to threads. There are two main scheduling methods, namely cooperative thread scheduling and preemptive thread scheduling

  • Collaborative thread scheduling

The thread execution time is controlled by the thread itself. After the thread completes its own work, it should inform the system to switch to another thread. The biggest benefit is that the implementation is simple, and the switching operation is known to the thread itself, with no thread synchronization problems. The downside is that thread execution time is out of control, and if a thread has a problem, it can stay blocked.

  • Preemptive thread scheduling

Each thread is allocated execution time by the system, thread switching is not determined by the thread itself. In Java, Thread.yield() yields execution time, but does not obtain it. Thread execution time is controlled by the system, and no single thread will cause the entire process to block.

Java thread scheduling is preemptive scheduling: If you want the system to allocate more time to some threads and less time to others, you can do this by setting thread priority. The Java language has 10 Thread priorities (thread.min_priority to thread.max_priority). When two threads are in the ready state at the same time, the Thread with the higher priority is more likely to be selected by the system for execution. However, priority is not very reliable, because Java threads are implemented through native threads mapped to the system, so thread scheduling ultimately depends on the operating system.

Thread is used in several ways

Sleep method

  • A thread calling sleep causes the current thread to go from the Runnable state to the TIMED_WAITING state without releasing the object lock.
  • The sleep method throws InterruptedException and clears the interrupt flag if the sleeping thread is interrupted by an interrupt method from another thread.
  • Threads may not get CPU time slices immediately after sleep.
  • If 0 is passed to sleep, it is the same as yield.

Yield method

  • A thread calling the yield method frees CPU resources and moves the current thread from Running to Runnable, giving higher priority (or at least the same) threads the opportunity to execute, but yield does not release the object lock.
  • If the current process has only main threads, when yield is called, the main thread will continue to execute because there is no thread with a higher priority.
  • The specific implementation depends on the task scheduling of the operating system.

The join method

Wait for the thread calling the JOIN method to finish, and then continue the program execution, generally used to wait for the asynchronous thread to complete the execution of the results before continuing to run. Code examples:

public class ThreadJoinDemo {

   public static void main(String[] args) throws InterruptedException {
      Thread thread = new Thread(() -> {
         System.out.println("t begin");

         try {
            Thread.sleep(5000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println("t finished");
      });

      long start = System.currentTimeMillis();
      thread.start();
      // Thread calls join, and main waits for thread to finish before continuing
      thread.join();
      
      System.out.println("Execution time:" + (System.currentTimeMillis() - start));
      System.out.println("Main finished"); }}Copy the code

Stop method

The stop method has been deprecated by the JDK because it is too violent and forces the thread to terminate halfway through execution. As you can see from the following example code, a call to stop by an unfinished thread releases the lock because using stop can cause data inconsistencies.

public class ThreadStopDemo {

   private static Object lock = new Object();

   public static void main(String[] args) throws InterruptedException {
      Thread thread = new Thread( ()->{
         synchronized (lock){
            System.out.println(Thread.currentThread().getName() + "Get lock");

            try {
               Thread.sleep(60000);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "Execution completed"); }}); thread.start(); Thread.sleep(2000);
      // Stop thread and release the lock
      thread.stop();

      new Thread(() -> {
         System.out.println(Thread.currentThread().getName() + "Waiting for lock");
         synchronized (lock){
            System.out.println(Thread.currentThread().getName() + "Get lock"); } }).start(); }}Copy the code

Interrupt mechanism for Java threads

Java does not provide a safe, straightforward way to stop threads. But interrupt mechanism is provided, interrupt mechanism is a cooperative mechanism, that is, through interrupt can not directly terminate another thread, but need to be interrupted by the thread itself. The interrupted thread has full autonomy and can choose to stop immediately, after a certain amount of time, or not stop at all.

  • Interrupt (): The interrupt sets the thread’s interrupt flag bit to true, but it does not stop the thread
  • IsInterrupted (): Checks whether the interrupt bit of the thread is true. This method does not clear the interrupt bit
  • Thread.interrupted(): Checks whether the interrupt flag of the current Thread is true and clears the interrupt flag and sets it to false

Gracefully stop threads using interrupt mechanisms

public class StopThreadDemo {

   public static void main(String[] args) throws InterruptedException {
      Thread thread = new Thread(() -> {
         int count = 0;
         // The interrupt flag is false and the thread continues execution
         while(! Thread.currentThread().isInterrupted()){ System.out.println("count = " + count++);
         }
         // The while condition does not hold, i.e. the interrupt flag bit is true, and the thread exits
         System.out.println("Thread stop: stop thread");
      });
      thread.start();

      Thread.sleep(500);
      // Set the interrupt flag bit to truethread.interrupt(); }}Copy the code

Note: When using the interrupt mechanism, be careful whether the interrupt flag bit is cleared

The thread is aware of the interrupt signal during sleep

When a thread is sleeping, the interrupt is called to signal the interruption, and the thread is aware of the interruption and throws an InterruptedException, clearing the interrupt signal and resetting the interrupt flag bit to false. This will lead to Thread the Thread. The currentThread. The isInterrupted () is false, such as the above code to the following code, not in the catch to manually add the interrupt signal, don’t do any processing, will block interrupt request, cause the Thread cannot stop right.

public class StopThreadDemo {

   public static void main(String[] args) throws InterruptedException {
      Thread thread = new Thread(() -> {
         int count = 0;
         try {
            Thread.sleep(3000);
         } catch (InterruptedException e) {
            e.printStackTrace();
            // Reset the thread interrupt status to true to prevent the thread from exiting properly
            //Thread.currentThread().interrupt();
         }
         // The interrupt flag is false and the thread continues execution
         while(! Thread.currentThread().isInterrupted()){ System.out.println("count = " + count++);
         }
         // The while condition does not hold, i.e. the interrupt flag bit is true, and the thread exits
         System.out.println("Thread stop: stop thread");
      });
      thread.start();

      Thread.sleep(500);
      // Set the interrupt flag bit to truethread.interrupt(); }}Copy the code

Sleep can be interrupted by throwing InterruptedException (interrupted) and clearing the interrupt flag

Communication between Java threads

volatile

The two main characteristics of volatile are visibility, which prohibits instruction reordering, and visibility, which allows threads to communicate.

public class VolatileDemo {

   public static volatile boolean flag = true;
   //public static boolean flag = true;

   public static void main(String[] args) throws InterruptedException {
      new Thread(() -> {
         while (true) {if (flag){
               System.out.println("turn on");
               flag = false;
            }
         }
      }).start();
      
      Thread.sleep(1);
      
      if(! flag){ System.out.println("turn off");
         flag = true; }}}Copy the code

Wake-on-wait (wake-on-notification) mechanism

  • The wake-up mechanism can be implemented using wait and notify methods. If the wait method of the thread lock object is called in a thread, the thread will enter the wait pair and wait until it is waked up by notify.
public class WaitNotifyDemo { private static Object lock = new Object(); private static boolean flag = true; public static void main(String[] args) { new Thread(() -> { synchronized (lock) { System.out.println("Thread1 get lock"); while (flag){ try { System.out.println("Start wait..." ); lock.wait(); Println ("Notified..."); // Gets into a blocking wait queue, waits to be awakened, and releases the lock system.out. println("Notified..." ); }catch (InterruptedException e){ e.printStackTrace(); } } System.out.println("Wait End..." ); } }).start(); new Thread(() -> { if (flag){ synchronized (lock){ System.out.println("Thread2 get lock"); if (flag){ lock.notify(); System.out.println("Notify lock..." ); flag = false; } } } }).start(); }}Copy the code
  • LockSupport is a tool used in the JDK to implement thread blocking and awakening. A call to locksupport.park () will wait for “permission” and a call to unpark will provide “permission” for a specified thread. You can use thread blocking in any situation, and you can specify any thread to wake up without worrying about the order of blocking and wake up operations, but note that the effect of multiple consecutive wakes is the same as that of one wake up.
package thread;

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {

   public static void main(String[] args) throws InterruptedException {
      Thread thread = new Thread(() -> {
         while (true) {// try {
// Thread.sleep(50);
// } catch (InterruptedException e) {
// e.printStackTrace();
/ /}
            System.out.println("ParkThread Start...");
            LockSupport.park();If there is a permit, it can consume the permit and continue executing. If there is no permit, it needs to wait for the permit and enter the blocking queue
            System.out.println("ParkThread gets a permit"); }}); thread.start(); System.out.println("1 sensei thread");
      LockSupport.unpark(thread);

      System.out.println("2 sensei thread");
      LockSupport.unpark(thread);
      // From the execution result of the program:
      // Locksupport. park and unpark need to be used in pairs, but there is no limit to the order in which park and unpark can be executed
      // If unpark is called several times in a row and then park is called consecutively, only the first time park is granted permission
      // But unpark needs to be called after thread.srart() to work
// thread.start();}}Copy the code

Pipeline input and output flows:

The piped I/O stream differs from the ordinary file I/O stream or network I/O stream in that the piped I/O stream is mainly used for data transfer between threads, and its transfer medium is memory. Pipeline input and output flows mainly include the following 4 specific implementations:

  • PipedOutputStream and PipedInputStream are byte oriented
  • PipedReader and PipedWriter are character oriented
public class PipedDemo {

   public static void main(String[] args) throws IOException {
      PipedWriter out = new PipedWriter();
      PipedReader in = new PipedReader();
      // Input streams and output streams need to be connected, otherwise IOException will be thrown when used
      in.connect(out);

      Printer printer = new Printer(in);
      Thread printerThread = new Thread(printer, "ThreadName-Print");
      printerThread.start();
      System.out.println("System start, you can input what you want to print in the console.");
      while (true){ out.write(System.in.read()); }}}class Printer implements Runnable{
   private PipedReader in;

   public Printer(PipedReader in){
      this.in = in;
   }
   @Override
   public void run(a) {
      try {
         while (true) {int receivedFromIn = in.read();
            if (((char) receivedFromIn) ! ='\n'){
               System.out.println("Printer received data: " + (char) receivedFromIn); }}}catch(IOException e) { e.printStackTrace(); }}}Copy the code

Thread.join

Join can be understood as a thread, when a thread calls in another thread of the join method, the current thread is blocked waiting for is called the join method can continue to perform the thread has been completed, so the benefits of the join can ensure the execution order of threads, but if the calling thread join method actually has lost the meaning of parallel, Although there are multiple threads, they are serial in nature, and the final implementation of join is actually based on wait notification mechanism