Java thread life cycle and state switching

premise

I've been a little lazy recently. I don't have any deep output. I just want to re read the source code implementation of JUC thread pool. Before that, I'd like to have an in-depth understanding of thread implementation in Java, including thread life cycle, state switching and thread context switching. At the time of writing this article, the JDK version used was 11.

Implementation of java thread

At jdk1 After 2, the java thread model has been determined to be implemented based on the operating system native thread model. Therefore, in the current or future JDK versions, the thread model supported by the operating system largely determines how the threads of the Java virtual machine are mapped, which can not be agreed on different platforms, and the virtual machine specification does not limit which thread model Java threads need to use to implement. The thread model only affects the concurrency scale and operation cost of threads. These differences are transparent to Java programs.

For Oracle sun JDK or Oracle sun JVM, its Windows version and Linux version are implemented using a one-to-one threading model (as shown in the figure below).

That is, a java thread is mapped to a light weight process, and a light weight thread is mapped to a kernel level thread. The thread we usually refer to usually refers to the light weight process (or, generally speaking, the java.lang.thread we usually create is a "handle" of the light weight process instance) , because a Java Lang. thread instance will correspond to a java thread instance in the JVM, and the java thread in the JVM should be understood as a lightweight process). By calculating the thread mapping relationship above, we can know that the Java. Net thread we create or operate in the application Lang. thread instances will eventually be mapped to the kernel threads of the system. If we maliciously or experimentally create Java. Thread instances indefinitely Lang. thread instance will eventually affect the normal operation of the system and even cause system crash (experiments can be done in the windows development environment to create and run java.lang.thread instance using an endless loop when sufficient memory is ensured).

There are two ways of thread scheduling, cooperative thread scheduling and preemptive thread scheduling.

Java threads will eventually be mapped to the native threads of the system kernel, so java thread scheduling ultimately depends on the system operating system. At present, the mainstream operating system kernel thread scheduling basically uses preemptive thread scheduling. That is, you can memorize it by rote: Java threads are scheduled using preemptive thread scheduling.

Many operating systems provide the concept of thread priority. However, due to the characteristics of the platform, the thread priority in Java does not match the system thread priority in different platforms. Therefore, java thread priority can only be understood as "recommended priority", which is generally Java Lang. thread #setpriority (int newpriority) does not necessarily take effect. It is possible that the priority of Java threads will be changed by the system itself.

State switching of Java threads

The status of Java threads can be changed from Java Java. Lang. thread's internal enumeration class Lang. thread $state knows:

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

The descriptions of these states are summarized as follows:

The switching relationship between thread states is as follows:

The following analyzes the state meaning and state switching of Java threads through API comments and some simple code examples.

New status

API notes:

/**
 * Thread state for a thread which has not yet started.
 *
 */
NEW,

A java thread instance that has just been created but has not been started (the thread#start () method has not been called) is in the new state.

public class ThreadState {

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread();
        System.out.println(thread.getState());
    }
}

// 输出结果
NEW

Runnable status

API notes:

/**
 * Thread state for a runnable thread.  A thread in the runnable
 * state is executing in the Java virtual machine but it may
 * be waiting for other resources from the operating system
 * such as processor.
 */
RUNNABLE,

When the java thread instance calls thread#start (), it will enter the runnable state. The runnable state can be considered to contain two sub states: ready and running.

When the java thread instance thread#yield() method is called or due to the scheduling of the thread scheduler, the state of the thread instance may change from running to ready, but the state obtained from the thread state thread#getstate() is still runnable. For example:

public class ThreadState1 {

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(()-> {
            while (true){
                Thread.yield();
            }
        });
        thread.start();
        Thread.sleep(2000);
        System.out.println(thread.getState());
    }
}
// 输出结果
RUNNABLE

Waiting status

API notes:

   /**
    * Thread state for a waiting thread.
    * A thread is in the waiting state due to calling one of the
    * following methods:
    * <ul>
    *   <li>{@link Object#wait() Object.wait} with no timeout</li>
    *   <li>{@link #join() Thread.join} with no timeout</li>
    *   <li>{@link LockSupport#park() LockSupport.park}</li>
    * </ul>
    *
    * <p>A thread in the waiting state is waiting for another thread to
    * perform a particular action.
    *
    * For example,a thread that has called {@code Object.wait()}
    * on an object is waiting for another thread to call
    * {@code Object.notify()} or {@code Object.notifyAll()} on
    * that object. A thread that has called {@code Thread.join()}
    * is waiting for a specified thread to terminate.
    */
    WAITING,

Waiting is an indefinite waiting state. Threads in this state will not be allocated CPU execution time. When a thread executes some methods, it will enter an indefinite waiting state until it is explicitly awakened. After being awakened, the thread state will change from waiting to runnable, and then continue to execute.

The thread #join () method is relatively special. It blocks the thread instance until the thread instance is executed. You can observe its source code 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) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - Now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            Now = System.currentTimeMillis() - base;
        }
    }
}

It can be seen that thread #join () always calls the object #wait () method when the thread instance is alive, that is, the blocking will not be released until the thread has completed execution and isalive () is false (which means that the thread life cycle has ended).

Based on the waiting status, for example:

public class ThreadState3 {

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(()-> {
            LockSupport.park();
            while (true){
                Thread.yield();
            }
        });
        thread.start();
        Thread.sleep(50);
        System.out.println(thread.getState());
        LockSupport.unpark(thread);
        Thread.sleep(50);
        System.out.println(thread.getState());
    }
}
// 输出结果
WAITING
RUNNABLE

Timed waiting status

API notes:

/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
*   <li>{@link #sleep Thread.sleep}</li>
*   <li>{@link Object#wait(long) Object.wait} with timeout</li>
*   <li>{@link #join(long) Thread.join} with timeout</li>
*   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
*   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,

Timed waiting is a finite waiting state. It is somewhat similar to waiting. Threads in this state will not be allocated CPU execution time. However, threads in this state do not need to be explicitly awakened. They only need to wait for the timeout period to arrive and will be awakened by the VM, which is somewhat similar to the alarm clock in real life.

for instance:

public class ThreadState4 {

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(()-> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                //ignore
            }
        });
        thread.start();
        Thread.sleep(50);
        System.out.println(thread.getState());
        Thread.sleep(1000);
        System.out.println(thread.getState());
    }
}
// 输出结果
TIMED_WAITING
TERMINATED

Blocked status

API notes:

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,

The blocked state is the blocked state. Threads in this state will not be allocated CPU execution time. When the thread status is blocked, there are two possible situations:

For a more detailed description, please refer to a previous article written by the author: in-depth understanding of the blocking and wake-up API provided by object

For scenario 1 above, take a simple example:

public class ThreadState6 {

    private static final Object MONITOR = new Object();

    public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(()-> {
            synchronized (MONITOR){
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    //ignore
                }
            }
        });
        Thread thread2 = new Thread(()-> {
            synchronized (MONITOR){
                System.out.println("thread2 got monitor lock...");
            }
        });
        thread1.start();
        Thread.sleep(50);
        thread2.start();
        Thread.sleep(50);
        System.out.println(thread2.getState());
    }
}
// 输出结果
BLOCKED

For scenario 2 above, take a simple example:

public class ThreadState7 {

    private static final Object MONITOR = new Object();
    private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws Exception {
        System.out.println(String.format("[%s]-begin...",F.format(LocalDateTime.Now())));
        Thread thread1 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread1 got monitor lock...",F.format(LocalDateTime.Now())));
                try {
                    Thread.sleep(1000);
                    MONITOR.wait();
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread1 exit waiting...",F.format(LocalDateTime.Now())));
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (MONITOR) {
                System.out.println(String.format("[%s]-thread2 got monitor lock...",F.format(LocalDateTime.Now())));
                try {
                    MONITOR.notify();
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    //ignore
                }
                System.out.println(String.format("[%s]-thread2 releases monitor lock...",F.format(LocalDateTime.Now())));
            }
        });
        thread1.start();
        thread2.start();
        // 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,确保thread1调用了Object#wait()
        Thread.sleep(1500);  
        System.out.println(thread1.getState());
        System.out.println(String.format("[%s]-end...",F.format(LocalDateTime.Now())));
    }
}
// 某个时刻的输出如下:
[2019-06-20 00:30:22]-begin...
[2019-06-20 00:30:22]-thread1 got monitor lock...
[2019-06-20 00:30:23]-thread2 got monitor lock...
BLOCKED
[2019-06-20 00:30:23]-end...
[2019-06-20 00:30:25]-thread2 releases monitor lock...
[2019-06-20 00:30:25]-thread1 exit waiting...

Scenario 2:

These three points seem a little around. You can understand them by watching them more and thinking about them more.

Terminated status

API notes:

/**
 * Thread state for a terminated thread.
 * The thread has completed execution.
 */ 
TERMINATED;

The terminated state indicates that the thread has terminated. A thread instance can only be started once. To be exact, the thread #run () method will only be called once. After the thread #run () method is executed, the thread state will change to terminated, which means that the thread life cycle has ended.

Take a simple example:

public class ThreadState8 {

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {

        });
        thread.start();
        Thread.sleep(50);
        System.out.println(thread.getState());
    }
}
// 输出结果
TERMINATED

Context switching

In a multithreaded environment, When the state of a thread changes from runnable to non runnable (blocked, waiting or timed_waiting), the context information of the corresponding thread (that is, context, including the contents of CPU registers and program counters at a certain point in time, etc.) needs to be saved so that the thread can continue to execute on the basis of the previous execution progress when it returns to the runnable state later. When a thread enters the runnable state from a non runnable state, it may involve restoring the previously saved thread context information and updating it And continue on this basis. The process of saving and restoring thread context information here is called context switch.

Thread context switching will bring additional performance overhead, including the overhead of saving and restoring thread context information The CPU time cost of thread scheduling and the cost of invalidation of CPU cache content (the variable value required by the code executed by the thread to access it from the CPU cache is higher than that from the main memory The variable value of the access response in (RAM) is much faster, but the thread context switching will invalidate the CPU cache content accessed by the relevant thread, generally the L1 cache and L2 cache of the CPU, so that the relevant thread will be rescheduled to the runtime later, and it has to access the variables in the main memory again to recreate the CPU cache content).

In Linux systems, you can view the number of global context switches through the vmstat command, for example:

$ vmstat 1

The running of Java programs can also be monitored by perf command in Linux system, for example:

$ perf stat -e cpu-clock,task-clock,cs,cache-reference,cache-misses java YourJavaClass

Resources mentioned that you can use the built-in tool Perfmon under windows (in fact, the task manager) to monitor the context switching of threads. In fact, the author did not find any way to view the context switching from the task manager. After searching, he found a tool: Process Explorer. Run a java program while running process explorer and check its status:

Because the breakpoint is hit, you can see that the context switching of the running program is more than 7000 times, and the current context switching increment of one second is 26 (because the author has set the process explorer to refresh the data every second).

Monitor thread status

If the project is running in a production environment, it is impossible to call the thread#getstate () method frequently to monitor the state change of the thread. JDK itself provides some tools to monitor thread status, as well as some open-source lightweight tools, such as Alibaba's Arthas. Here's a brief introduction.

Using jvisual VM

Jvisualvm is the heap and thread waiting JVM indicator monitoring tool of JDK, which is suitable for development and test environment. It is located in Java_ Under the home / bin directory.

The button of thread dump is similar to the jstack command mentioned below, which is used to export the stack information of all threads.

Using jstack

Jstack is a command-line tool built into JDK. Its function is to obtain the thread stack information of the java process with the specified PID. For example, if the PID of an idea instance running locally is 11376, you only need to enter:

jstack 11376

Then the console output is as follows:

In addition, if you want to locate the PID of a specific java process, you can use the JPS command.

Use JMC

JMC, that is, Java mission control, is also a tool of JDK. It provides more powerful functions than jvisualvm, including MBean processing, thread stack status viewing, flight recorder, etc.

Summary

Understanding the java thread state switching and some monitoring methods is more conducive to the daily development of multithreaded programs and problems in the production environment, By monitoring the stack information of threads, you can quickly locate the root cause of the problem (generally speaking, the current mainstream MVC applications (to be exact, servlet containers such as Tomcat process a single request through one thread. When the request is blocked, the thread exporting the corresponding request can basically locate the exact location of the blocking. If message queues such as rabbitmq are used, the blocking of consumer threads can also be solved by using similar ideas).

(end of this paper e-a-20200804 c-3-d)

The official account of Throwable (id:throwable-doge), which has been pushing the design of architecture, concurrent, and source code indefinite, is an original article:

This is the original article released by the official account Throwable, which is included in the album "concurrent programming".

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>