Java thread implementation and thread state

Keywords: Java JDK Windows Linux

Write at the top

I wrote Concurrent HashMap before, whether 1.8 or 1.7, which involves locks Synchronized and ReentrantLock; the concept of locks is to solve thread security issues; so start writing from threads.
This article mainly writes about the implementation of Java threads and thread status, as well as the concept of context, there are some imperfections, will continue to check and fill omissions.

Implementation of Java Thread

Java thread model is based on native thread model of operating system. In JDK, windows version and Linux version are implemented with one-to-one thread model.

As you can probably see from the figure above, a java thread corresponds to a lightweight process (LWP), and a lightweight thread maps to a kernel thread (KLT). Threads are often referred to as lightweight processes. The java. lang. Thread instances that we create or operate on in our applications eventually map to the system's kernel threads. If you create an unrestricted instance of java.lang.Thread, it will affect the normal operation or crash of the system.

Thread scheduling can be divided into two types, collaborative thread scheduling and preemptive thread scheduling.

Java threads will eventually be mapped to native threads in the system kernel, so Java thread scheduling ultimately depends on the operating system. At present, preemptive thread scheduling is basically used in the mainstream operating system, and Java threads also use preemptive thread scheduling to schedule threads.

Many systems provide the concept of priority. Because of the platform characteristics, the thread priority in Java does not match the system thread priority in different platforms, so the Java thread priority can only be understood as "recommended priority". Simply put, java. lang. Thread_ setPriority (int new Priority) does not necessarily work. It is possible that the priority of Java threads will be changed by the system itself.

Status of Java Threads

From the JDK source code to find the internal enumeration class of Thread class, we know that threads have six states: new, runnable, blocking, indefinite waiting, finite waiting, termination.

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

The following two graphs describe the state and state switching of threads very well.

Thread status:

Thread switching

NEW

Threads are created in the NEW state before they are started.

    Thread thread = new Thread();
    System.out.println(thread.getState());	//NEW

RUNNABLE

After the thread calls the start method, it enters the RUNNABLE state. RUNNABLE has two sub-states, READY and RUNNING.

  • ready: This stateful thread can be scheduled by the thread scheduler to become a RUNNING state
  • RUNNING: This state thread indicates that the thread is running and that the code instructions in the run method in the thread object are scheduled by the CPU.

When the Thread.yeild() method is executed or due to thread scheduler scheduling, the thread state may change from RUNNING to READY. But the status returned using thread.getState() is still RUNNABLE.

    Thread thread = new Thread(()->{
        while(true){
            Thread.yield();
        }
    });
    thread.start();
    TimeUnit.SECONDS.sleep(2);
    System.out.println(thread.getState());		//RUNNABLE

BLOCKING

This state is well explained in the source code, and the source document comments are placed directly.

        /**
         * 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,        

This state indicates that a thread is blocking waiting to acquire a monitor lock.

A blocked state thread waits for a monitor lock to enter the synchronization block or method or to re-enter the synchronization block or method after calling Object#wait().

The above two situations need to be explained in a more popular way:

  1. A blocked state thread waits for a monitor lock to enter a synchronization block or synchronization method

    This is a good understanding. Threads are waiting for a listener lock. Only after acquiring the listener lock can they enter the synchronized code block or synchronized method. In the process of waiting for acquiring the lock, they are all blocked.

        	private static Object lock = new Object();
    		Thread thread1 = new Thread(()-> {
                synchronized (lock){
                    try {
                        Thread.sleep(Integer.MAX_VALUE);
                    }
                    catch (InterruptedException e) {}
                }
            });
            Thread thread2 = new Thread(()-> {
                synchronized (lock){
                }
            }
            );
            thread1.start();
            Thread.sleep(50);
            thread2.start();
            Thread.sleep(50);
            System.out.println(thread2.getState());		//BLOCKED
    
    
  2. Re-enter the synchronization block or method after calling Object wait ().

    When a thread executes a synchronized code block or code in a synchronized method, it calls the Object wait method, which requires other threads to call Object notify ()/notifyAll () for display wake-up. While other threads wake up the thread, other threads do not terminate their synchronized code block or synchroni. The Zed method releases the lock, so the thread is then blocked.

    import java.util.concurrent.TimeUnit;
    
    public class WaitNotify {
        private static Object lock = new Object();
        private static boolean flag = true;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(new Wait(),"Wait");
            thread1.start();
            TimeUnit.SECONDS.sleep(2);
            Thread thread2 = new Thread(new Notify(),"Notify");
            thread2.start();
        }
    
        static class Wait implements Runnable{
            @Override
            public void run() {
                synchronized (lock){
                    while (flag){
                        try {
                            lock.wait();
                            System.out.println(Thread.currentThread().getName() + "flag is true");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName() + "flag is false");
                }
            }
        }
    
        static class Notify implements Runnable{
            @Override
            public void run() {
                synchronized (lock){
                    flag = false;
                    lock.notifyAll();
                    try {
                        TimeUnit.SECONDS.sleep(10);		//At this point the wait thread is in the BLOCKED state
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

WAITING

The reason why a thread enters an indefinite waiting state is that one of the following methods is called:

  1. Object wait () without timeout
  2. Thread#join() without timeouts
  3. LockSupport.park()

Threads that call the above methods wait for another thread to process them, as follows:

  1. A thread that calls Object wait () waits for another thread to call Object notify () or Object notifyAll () to wake up
  2. A thread that calls Thread join () waits for another thread to terminate
  3. Called LockSupport.park() and waited for LockSupport.unpart(thread) to wake up

The Thread join () method is special in that it blocks the thread instance until the thread instance is executed. The following is the JDK 1.8 source code:

    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()) {	//When the current thread survives, it will always block
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

CPU execution time is not allocated in WAITING state until wake-up is displayed. Thread state changes from WAITING to RUNNABLE and then continues execution.

		Thread thread = new Thread(()-> {
            LockSupport.park();
            while (true){
                Thread.yield();
            }
        }
        );
        thread.start();
        Thread.sleep(50);
        System.out.println(thread.getState());		//WAITING

TIMED WAITING

The reason why a thread enters a finite waiting state is that one of the following methods is invoked:

  1. Thread.sleep(long millis)
  2. Object wait (long timeout) with timeout
  3. Thread#join(long millis) with timeout
  4. LockSupport.parkNanos(Object blocker, long nanos)
  5. LockSupport.parkUntil(Object blocker, long nanos)

CPU execution time is not allocated in TIMED WAITING state, but no wake-up is required in this state, and VM wakes up when the time-out is reached.

		Thread thread = new Thread(()-> {
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {}
        }
        );
        thread.start();
        Thread.sleep(50);
        System.out.println(thread.getState());		//TIMED WAITING

TERMINATED

After the thread is executed, it will enter the TERMINATED state.

A thread can only be started once, and the Thread#run() method is called once to enter this state, proving the end of the thread life cycle.

		Thread thread = new Thread(() -> {
        	}
        );
        thread.start();
        Thread.sleep(50);
        System.out.println(thread.getState());		//TERMINATED

Context switching

In a multithreaded environment, when the thread state is switched from RUNNABLE to non-RUNNABLE, context information needs to be saved. Conversely, when switching from non-RUNNABLE to RUNNABLE, context information needs to be read to continue execution on previous progress.

The preservation and recovery of thread context information is called context switching.

Reference resources

  1. JDK 1.8 source code

  2. Java Thread Life Cycle and State Switching

Posted by phychion on Fri, 06 Sep 2019 23:22:43 -0700