Java Thread State Transition & How to Stop Thread

1. State transition of Java threads

State Transition Diagram for 1.1 Java Threads

  • Java threads may be in six different states throughout their life cycle, but they can only be in one state at any time
  • As the code executes, Java threads switch between different states, as shown in the diagram

Description of 1.2 status

NEW Status

  • Create a new thread object, but have not yet called the start() method
  • Thread class: A thread class that implements the Runnable interface or inherits the Thread class

RUNNABLE Status

  • When a thread object is created, other threads invoke the thread's start() method, which will be in the RUNNABLE state and in the runnable thread pool

  • In Java threads, the ready state (READY) and the runtime (RUNNING) are collectively referred to as the runtime (RUNNABLE)

    In JVM, threads in RUNNABLE may be waiting for other resources, such as CPU s
    That is, the runtime also contains the ready state

  • As you can see, in Java's thread state, there are actually no RUNNING and READY

  • Causes collectively referred to as runtime:

    • Java threads correspond to operating system threads, and the scheduling of threads is essentially determined by the operating system
    • Thread state in JVM is essentially a mapping and wrapping of the underlying state
    • Threads will enter READY state because CPU time slices are exhausted, CPU time slices are preempted, or even voluntarily relinquish CPU time slices
    • Switching between RUNNING state and READY state is very frequent
    • While Java thread state is for monitoring services, it is meaningless to distinguish between them because they switch so quickly
    • Therefore, it is a good choice to collectively refer to the RUNNING and READY states as RUNNABLE States

READY status

  • Threads in RUNNABLE state, in READY state because they wait for resources
  • Threads may enter READY state if
    • NEW → \rightarrow _READY: After creating a thread, call its start() method
    • RUNNING → \rightarrow _READY: Threads in RUNNING state, CPU time is exhausted, CPU time slices are preempted, and CPU time slices are voluntarily relinquished by yield() method
    • WAITING/ TIMED_WATING → \rightarrow _READY: Threads in a waiting state, automatically returned when notified or timed out
    • BLOCKED → \rightarrow _READY: Obtain object locks from threads blocked by waiting for object locks

RUNNING Status

  • Threads in the runnable thread pool, selected by the thread dispatcher (getting CPU time slices), will be in RUNNING state

BLOKED Status

  • When a thread enters a synchronized method or synchronized code block, it waits for an object lock to be acquired, thereby entering a blocked state

WAITING Status

  • Executing certain methods will put the thread into a waiting state and need to be explicitly waked up, otherwise it will be waiting indefinitely
  • Threads in a waiting state will not be allocated CPU time slices
  • Here are the ways to enter and exit the waiting state
  • Be careful
    • Passive and active: The blocking state is when threads passively "wait" to acquire object locks; Waiting state is when a thread actively calls a specific method to wait for certain conditions to be met
    • Also wait for lock acquisition, different states: enter synchronization method or synchronization code block, thread is blocked; The lock() method is executed in a wait state because its implementation is based on the related methods in the LockSupport class

TIMED_WATING Status

  • After a thread has been put into a wait state by calling a specific method, it will wait until it is explicitly awakened
  • Therefore, you can increase the timeout limit on the basis of the wait state to avoid waiting all the time
  • Here are the ways to enter and timeout wait states

TERMINATED status

  • Thread terminates execution of run() or call() methods or exits abnormally
  • Once a thread terminates, it cannot regenerate: calling the start() method again on the terminated thread throws a java.lang.IllegalThreadStateException exception.
  • Note: The main thread exits and the child thread does not end immediately; Unless they are daemon threads (there are no non-daemon threads in the JVM that exit automatically)

Reference documents:

1.3 Answers to some hot questions

1.3.1 End of main thread, end of sub-thread immediately?

Wrong Cognition: Main Thread Thread Ends, Subthread Ends Immediately

  • I don't know when I'm impressed with the main thread and the child threads:

    • Create a thread in the main() method, and leave the main thread waiting for a while without jion(), sleep(), and so on
    • Once the main thread finishes executing, the child thread finishes immediately
    • The end here is not really the end, but the printout information for the child threads will no longer appear in standard output.
    • So I would think the main thread ended and the sub-threads ended
  • Like the following code:

    • In my memory: once the main thread has finished the last sentence of printing, the printing of the sub-threads will also stop, giving the illusion that the sub-threads will also end
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
        });
        thread.start();
        System.out.println("End of Main Thread");
    }
    
  • Communicate with colleagues, colleagues say that they also recognize this.

  • But this time, as soon as the code executes, it finds that the main thread ends and the sub-threads continue printing 😂

Cognitive Errors

  • On second thought, the above perceptions are irrational
  • In Java programs, any thread must eventually be created by its own parent.
  • Parent threads create child threads to perform certain tasks
  • Tasks in child threads may still be executing, because the parent thread ends and those tasks end violently, so Java programs get messy
  • So, if you think about it, that's certainly not reasonable

When does the JVM exit

  • In Java programs, the main thread (non-daemon thread) ends with the main() method execution.
  • If there are no other non-daemon threads in the JVM, the JVM will exit
  • At this point, all daemon threads in the JVM need to terminate immediately
  • Summary:
    • Once a non-daemon thread does not exist in the JVM, the JVM exits.
    • This causes the daemon thread to terminate immediately

If there are no other non-daemon threads, the main thread ends and the daemon thread immediately ends

  • In the main method, create a daemon thread that prints 0-9. Once the main thread exits, the daemon also stops printing

    public static void sleep(TimeUnit timeUnit, long timeout) {
        try {
            timeUnit.sleep(timeout);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                sleep(TimeUnit.MILLISECONDS, 5);
            }
        });
        // Set as Daemon Thread
        thread.setDaemon(true);
        thread.start();
        // Wait for a daemon thread to execute for a while before the main thread finishes again
        sleep(TimeUnit.MILLISECONDS, 10);
        System.out.println("End of Main Thread");
    }
    
  • The results are as follows:

Reference documents:

1.3.2 daemon thread

  • From the definition of when the JVM exits, daemon threads are more suitable as worker threads, such as background dispatch, indicator collection

  • Once a thread is created, it can be set as a daemon thread through the Threa.setDaemon() method

  • Be careful:

    • Daemon thread setup, needs to be completed before starting the thread (start() method)
    • Due to the special nature of the daemon thread, the finally statement cannot be relied on to finish the work
  • The example code for the daemon thread above, plus the following finally statement block, does not ultimately execute the code in the finally statement block

    finally {
        System.out.println("The daemon thread will exit and start recycling resources");
    }
    

What is the effect of the 1.3.3 yield() method?

Understanding of yield

  • Thread actively calls yield() method, which can change from RUNNING state to READY state
  • I don't really know what the yield() method does.
  • The Chinese definitions of the English word yield are: yielding, giving up, giving in
  • Based on previous thread state learning, RUNNING threads occupy CPU time slices, and READY threads need to wait for other resources, including CPU time slices
  • Thus, yield in yield() method is interpreted as more reasonable to compromise or abandon

yield() method

  • The yield() method is defined as follows: it is a static, native method

    public static native void yield();
    
  • The method's comments in the source code are translated roughly as follows

    • Prompt the dispatcher that the current thread is willing to give up its processor resources by calling the yield() method
    • The dispatcher is free to ignore this prompt: the thread that the dispatcher eventually chooses to execute includes itself, and it looks like it has not made a concession
    • yield is a heuristic attempt to improve the relative progress of threads and avoid overuse of CPU s by some threads
    • yield is more suitable for debugging or testing
  • Summary:

    • By calling the yield() method, the current thread gives up its own CPU for use by other threads, including itself
    • This is more appropriate when the current thread has completed important work and can defer execution

Reference link:

1.4 Programming Experience State Change of Java Threads

1.4.1 "Life and Death" of Ordinary Threads

  • If there are no special operations such as lock acquisition, active sleep, wait, etc., the running state of the thread is: NEW → \rightarrow → RUNNABLE → \rightarrow → TERMINATED
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Subthread starts running, Subthread state: " + Thread.currentThread().getState());
        });
        System.out.println("Create a sub-thread, sub-thread state: " + thread.getState());
        // Start Subthread
        thread.start();
        // Waiting for the end of subthread execution
        sleep(TimeUnit.SECONDS, 1);
        System.out.println("End of Subthread Execution, Subthread Status: " + thread.getState() + ", isAlive: " + thread.isAlive());
    }
    
  • The results are as follows, as expected

1.4.2 Blocking & Timeout Waiting

  • The code below shows two main states of thread blocking and timeout waiting
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Blocked(), "threadA");
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        Thread thread2 = new Thread(new Blocked(), "threadB");
        thread2.start();
    
        // Thread A enters a timeout wait state due to sleep
        System.out.println(thread1.getName() + "Because sleep And enter a timeout waiting state:" + thread1.getState());
        // Thread B is blocked by waiting for a lock
        System.out.println(thread2.getName() + "Blocked due to waiting for lock:" + thread2.getState());
    }
    
    class Blocked implements Runnable {
        @Override
        public void run() {
            synchronized (Blocked.class) {
                System.out.println(Thread.currentThread().getName() + "Acquire lock, in" +
                        Thread.currentThread().getState() + "state");
                // Hibernate for a while after acquiring the lock
                sleep(TimeUnit.SECONDS, 10)
            }
        }
    }
    
  • The results are as follows, as expected

2. Start or terminate threads

2.1 Thread Creation

  • Create a thread through the constructor of the Thread class and eventually invoke the private init() method to initialize the thread

  • The init method is complex, so here's just the key code

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        // Set Thread Name
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        // Thread name, default to Thread-xx
        this.name = name;
    	// The current thread that created this thread is the parent thread
        Thread parent = currentThread();
        // Omit code related to SecurityManager
    	
    	// Thread group, unstarted threads plus 1
        g.addUnstarted();
    	
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        // Code is omitted
        this.target = target;
        setPriority(priority);
        // Code is omitted
        this.stackSize = stackSize;
    	// Thread ID, incremental from 1
        tid = nextThreadID();
    }
    
  • From init's code, initializing a thread requires setting the thread name, thread group, whether it is a daemon thread, priority, Runnale object, stack size, thread ID, and so on.

  • By default, inheriting the priority of the parent thread, whether it is a daemon thread, you need to set the thread's own priority and whether it is a daemon thread through methods setPriority(), setDaemon()

Start of 2.2 Thread

  • From the state transition of Java threads, newly created threads must be started by the start() method in order to run

  • The comments for the start() method are as follows

    • Calling threadB.start() on the current thread (assuming thread A) allows thread B to start running, and the JVM calls the run() method on thread B
    • At this point, thread A and thread B execute concurrently
    • You cannot call a thread's start() method repeatedly, even when execution is complete
  • The start() method code is as follows:

    public synchronized void start() {
        // 0 means the thread is in NEW state, as long as it is not.
        // This means that the thread has started and IllegalThreadStateException is thrown
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        // Log threads that have been started in the thread group, not started threads nUnstartedThreads minus 1
        group.add(this);
    
        boolean started = false;
        try {
            start0(); // Invoke native method to start thread
            started = true; // Start Successfully
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this); // Thread Start Failed, Back to Unstarted Threads Queue
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    
  • native's start0() method will eventually call the JVM_StartThread() method → \rightarrow → new JavaThread() → \rightarrow → os::createThread() → \rightarrow → pthread_create()

2.2.1 run() vs start()

  • The Thread class is defined as follows, which implements the Runnable interface

    public class Thread implements Runnable
    
  • As described earlier, code in the run() method is executed when the thread starts

  • Thread implements the run() method of the Runnable interface as follows, which actually executes the run() method of the target passed in to the Runnable type

    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
  • Question: Since the Thread class itself has a run() method, why not call the run() method directly?

  • My guess is that the start() method creates a thread at the operating system level to support multithreaded execution; The run() method does not create a new thread, and the execution of its code is done in the current thread

  • Verification: the run() method is executed in the current thread

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("implement run()Threads of methods:" + Thread.currentThread().getName());
            for (int i = 0; i < 5; i++) {
                System.out.println(i);
                sleep(TimeUnit.MILLISECONDS, 1);
            }
        });
        thread.run();
        System.out.println("Execute the main thread code, and the status of the child thread is" + thread.getState());
    }
    
  • The results of the program run show that:

    • The thread's run() method is called directly, and its code executes in the current thread.
    • Execution of the run() method will not continue until the current thread's subsequent code is executed
  • The differences between the start() and run methods are summarized as follows

    • The start() method creates and starts a new thread and executes run code in the new thread without affecting subsequent code execution for the current thread - the start() method implements multithreading
    • run() does not create a new thread and executes run code directly on the current thread. The run () method does not implement multithreading
    • The start() method can only be called once, and a second call will throw an IllegalThreadStateException exception. The run() method can be called multiple times

2.3 suspend(), resume(), stop() threads

  • Like the reader you used earlier, sometimes we want a thread to pause execution, resume execution, or even stop execution directly

  • The suspend() method suspends execution, the resume() method resumes execution, and the stop() method stops execution.

    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Thread thread = new Thread(() -> {
            while (true) {
                // Every second, say hi
                sleep(TimeUnit.SECONDS, 1);
                System.out.println(format.format(new Date()) + "\t hi");
            }
        });
        thread.start();
        // Execute for a period of time, pause the thread
        sleep(TimeUnit.SECONDS, 3);
        thread.suspend();
        System.out.println(format.format(new Date()) + " Main Thread Suspends Subthreads, Subthread state:" + thread.getState());
        // Restore child threads after 3 seconds
        sleep(TimeUnit.SECONDS, 3);
        thread.resume();
        System.out.println(format.format(new Date()) + " Main Thread Recovery Subthread, Subthread state:" + thread.getState());
        // Stop the child thread after 3 seconds
        sleep(TimeUnit.SECONDS, 3);
        thread.stop();
        System.out.println(format.format(new Date()) + " Main Thread Stops Subthread");
        sleep(TimeUnit.SECONDS, 1);
        // Print as soon as stop, may not have finished stop, print out RUNNABLE
        System.out.println("Subthread state:" + thread.getState());
    }
    
  • The results are as follows:Because of multithreaded execution, the main thread restores the possible exchange order between the print of the child threads and the child threads

  • As you can see from the execution results, the above methods successfully suspend, resume, and stop threads

  • In IDE, these methods are shown to be obsolete and not recommended

    • The suspend() method does not release occupied resources into sleep, so the thread is in TIMED_WAITING Status
    • The stop() method does not guarantee the normal release of threads'resources when it stops thread execution

2.4 interrupt() Stop Thread

2.4.1 interrupt() method

  • Interrupts, interrupt responses, and so on are often mentioned in courses where computational composition is similar

  • Calling the threadB.interrupt() method in thread A is like saying hello to thread B from thread A, which responds by checking to see if it has been interrupted

  • Whether it is interrupted or not can be determined by the isInterrupted() method. This method returns true, indicating that it was interrupted

  • Exceptions:

    • If a thread is in an uninterruptible operation due to join, sleep, wait, etc., the interrupt identifier is cleared before throwing the InterruptedException
    • At this point, the isInterrupted() method will return false
  • The method notes for interrupt() also cover interrupts to I/O operations, which you can learn more about if you need to

  • The following code shows the status of the interrupt identifier after calling the interrupt() method on different threads

    public static void sleep(TimeUnit timeUnit, long timeout) {
        try {
            timeUnit.sleep(timeout);
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "Interrupted, Interrupt Identification: " + Thread.currentThread().isInterrupted());
            // Stop current thread after interruption
            Thread.currentThread().stop();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            while (true) {
                sleep(TimeUnit.SECONDS, 1);
            }
        }, "thread A");
        Thread thread2 = new Thread(() -> {
            while (true) {
                for (int j = 0; j < 100000000; j++) {
                    for (int i = 0; i < 1000000000; i++) {
    
                    }
                }
                System.out.println("Complete Count Once");
            }
        }, "thread B");
    
        thread1.start();
        thread2.start();
    
        // Interrupt Thread
        sleep(TimeUnit.MILLISECONDS, 200);
        thread1.interrupt();
        thread2.interrupt();
        // Thread A stopped execution because sleep was interrupted, Thread B was unaffected and continued execution
        sleep(TimeUnit.MILLISECONDS, 100);
        System.out.println(thread1.getName() + "Status:" + thread1.getState() + ", isInterrupted: "
                + thread1.isInterrupted() + ", " + thread2.getName() + "Status:" + thread2.getState()
                + ", isInterrupted: " + thread2.isInterrupted());
        thread2.stop();
    }
    
  • You can see from the execution results

    • The interrupt identifier is cleared for a thread in sleep before throwing an InterruptedException exception
    • Interrupts do not affect the execution of normal threads

2.4.2 Stop threads by interrupt identification

  • From the example above, you can see that when an ordinary thread is interrupted, the interrupt is identified as true

  • You can take advantage of this feature to stop thread execution, such as exiting from a loop

    Thread thread = new Thread(() -> {
        Thread currentThread = Thread.currentThread();
        while (!currentThread.isInterrupted()) {
            // do something
        }
        // Exit loop after break, stop execution
        System.out.println(currentThread.getName() + "interrupt: "+ currentThread.isInterrupted()  + ", Stop execution");
    }, "thread1");
    thread.start();
    sleep(TimeUnit.SECONDS, 1);
    // Interrupt Thread
    thread.interrupt();
    

2.4.3 How to continue responding to the next interruption

  • Sometimes, we want the thread to do something in response to the interrupt, then continue working, and respond to the interrupt

  • This requires clearing the interrupt identifier after responding to the interrupt to ensure that it can continue responding to the interrupt

  • You can use the interrupted() method to get the interrupt identity of the current thread and clear it to continue responding to interrupts

  • The code example is as follows:The interrupted() method allows you to respond to interrupts multiple times

    Thread thread = new Thread(() -> {
        while (true) {
            if (Thread.interrupted()) { // Interrupt identified as true, response to interrupt
                System.out.println(Thread.currentThread().getName() + "interrupt: " + Thread.currentThread().isInterrupted());
            } else {
                // do other things
            }
        }
    }, "thread1");
    thread.start();
    sleep(TimeUnit.SECONDS, 1);
    // Interrupt Thread
    thread.interrupt();
    sleep(TimeUnit.SECONDS, 1);
    // Continue interrupting threads, interrupts can be responded to
    thread.interrupt();
    
  • results of enforcement

  • Carefully, you will find that the interrupted() method is static because its purpose is to clear the fetch and clear the current thread!!! Interrupt Identification

2.5 Stop threads by sharing variables

  • Volatile variable guarantees memory visibility and can be used to stop threads in time through volatile type shared variables

    class MyThread extends Thread {
        public volatile boolean exit = false;
    
        @Override
        public void run() {
            while (!exit) {
                // do something
            }
            System.out.println("The condition is met and the thread stops when the first and last work is completed");
        }
    }
    
    public static void main(String[] args)  {
        MyThread thread = new MyThread();
        thread.start();
        // Interrupt threads by sharing variables while running
        sleep(TimeUnit.SECONDS, 1);
        thread.exit = true;
    }
    

3. Summary

  • This time, I mainly learned the state transition of threads, the start and stop of threads.

State Transition of Threads

  • Java threads actually have only six states, of which the RUNNABLE state contains both RUNNIG and READY States
  • Java thread state transitions, such as when to enter or exit the wait/timeout wait state, when to enter the READY state, when to enter or exit the blocking state, and so on

Some small things

  • yield() method: the current thread voluntarily relinquishes the CPU time slice for use by others, including itself
  • Java threads are unrelated: parent threads end, child threads are unaffected
  • JVM exit is related to the existence of non-daemon threads: JVM exit as long as no non-daemon threads exist

Thread Start

  • A new thread is created, but the parameters are initialized, not really at the operating system level
  • start() method: call the native method and eventually create a thread at the operating system level; Non-repeatable call
  • start() method vs run() method: the former can be multi-threaded and cannot be called repeatedly; The latter cannot be multithreaded and can be called repeatedly

Method of thread stopping

  • By imperfect stop() method
  • By the interrupt() method + isInterrupted() method:
    • interrupt() method: if a thread is performing an interrupt operation, the interrupt identifier is cleared
    • isInterrupted() method: Returns true if the thread is interrupted
    • Special interrupted() method: Returns the interrupt condition and clears the interrupt identifier, which can be used to repeatedly respond to interrupts
  • Shared variable: volatile variable guarantees memory visibility and can be used for thread exit identification

Reference link:

Keywords: Java thread

Added by ectraz on Tue, 09 Nov 2021 18:55:45 +0200