preface

In this article, we’ll talk about thread state transitions and some of the more important methods we use.

Again, this article uses source code analysis to understand this knowledge.

This article source based on JDK1.8.

By the end of reading this article, you should be able to answer the following frequently asked questions:

What are the states of a thread and the transitions between them?

  1. What is the difference between thread.sleep () and thread.currentThread ()?
  2. What is the difference between thread.sleep () and Object#wait()?
  3. What is the difference between thread.sleep () and thread.yield ()?
  4. What is your understanding of the join method?

Thread state

In the Thread class, Thread State is achieved through the threadStatus attribute and the State enumeration class:

/* Java thread status for tools, * initialized to indicate thread 'not yet started' */
private volatile int threadStatus = 0;


public enum State {
    /** * Thread state for a thread which has not yet started. */
    NEW,

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

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

    /**
     * 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 <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

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

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

/**
 * Returns the state of this thread.
 * This method is designed for use in monitoring of the system state,
 * not for synchronization control.
 *
 * @return this thread's state.
 * @since 1.5 * /
public State getState() {
    // get current thread state
    return sun.misc.VM.toThreadState(threadStatus);
}
Copy the code

It can be seen from the source code that the thread has a total of six states, and its state transformation relationship is shown as follows:

It is important to note that the RUNNABLE state includes what are commonly referred to as running and ready states from the state definition.

Commonly used method

CurrentThread is defined as follows:

/**
 * Returns a reference to the currently executing thread object.
 *
 * @return  the currently executing thread.
 */
public static native Thread currentThread();
Copy the code

As you can see, it is a static method and a native method that returns the thread currently executing.

Now that we have multiple CPU cores, we can have multiple threads running on different CPU cores at the same time, so which one of the threads is currently executing?

In fact, “currently executing thread” refers to the thread that is currently executing this code.

CurrentThread (); Thread (); Thread (); Thread ();

public class ThreadTest {
    public static void main(String[] args){ System.out.println(Thread.currentThread().getName()); }}Copy the code

We know that when a Java program starts, a Thread immediately starts running. This is usually called the Main Thread. The Main Thread will execute the Java entry method Main, so the Thread currently executing thread.currentThread () is the Main Thread.

sleep

The two most commonly asked questions about the sleep method are:

  1. What is the difference between thread.sleep () and thread.currentThread ()?
  2. What is the difference between thread.sleep () and Object.wait()?

The answers to these questions can be found in the source code. Let’s look at the source code directly:

/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds, subject to
 * the precision and accuracy of system timers and schedulers. The thread
 * does not lose ownership of any monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;
Copy the code

A sleep method is a static one and a native one. Causes the currently executing thread to sleep is a executing thread. So we can answer the above question:

Thread.sleep() is no different from Thread.currentThread().sleep() if anything, it is that one calls a static method directly from a class and the other calls a static method from an instance of a class.

In addition, there is a very important sentence in the comments above:

The thread does not lose ownership of any monitors. That is, although the sleep function causes the current thread to surrender CPU, the current thread still holds the monitor lock it acquired, unlike the WAIT method that cedes CPU and monitor lock resources at the same time. There is another version of the sleep method:

/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds plus the specified
 * number of nanoseconds, subject to the precision and accuracy of system
 * timers and schedulers. The thread does not lose ownership of any
 * monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @param  nanos
 *         {@code 0-999999} additional nanoseconds to sleep
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative, or the value of
 *          {@code nanos} is not in the range {@code 0-999999}
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public static void sleep(long millis, int nanos) throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException("nanosecond timeout value out of range");
    }

    if (nanos >= 500000|| (nanos ! =0 && millis == 0)) {
        millis++;
    }

    sleep(millis);
}
Copy the code

Time delay parameters of this method with nanosecond level, but look at the source code, we knew that, the more the nanosecond level of delay and no use, finally the function or call the above method of single parameter native sleep latency and millisecond level, the extra parameter is to make the most current 1 millisecond delay time of millisecond level. Remember the wait method we talked about last time? Let’s compare:

public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException("nanosecond timeout value out of range");
    }

    if (nanos > 0) {
        timeout++;
    }

    wait(timeout);
}
Copy the code

How’s that? Is it similar? There is only a slight difference in the carry from nanosecond to millisecond, which I suspect is due to historical reasons.

Sleep (0) does not have an argument version, so sleep(0) does not have an argument version.

This is not mentioned in the source code, but by guessing the definition of the sleep method, we know that it gives 0 milliseconds to the CPU. This may sound trivial, but the current Thread that called Thread.sleep(0) is actually “frozen”, giving other threads a chance to execute first. This means that the current thread frees some unused timeslices to be used by other threads or processes, which is equivalent to yield.

yield

Now that we’ve talked about the sleep(0) method, we have to yield it:

/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control  constructs such as the ones in the * {@link java.util.concurrent.locks} package.
 */
public static native void yield(a);Copy the code

Yield method is also a native method, A hint to the scheduler that the current thread is willing to yield its current use of A processor scheduler is free to ignore this hint. It is simply a suggestion to the CPU that the current thread is willing to give up the CPU to another thread. Depending on the vendor’s behavior, it is possible for a thread to yield the CPU and then immediately acquire it. In contrast, the sleep method always frees up CPU resources and sleeps for a specified period of time without competing for CPU resources.

So calling yield does not exit the RUNNANLE state. At best, it changes the thread from RUNNING to ready, but it is possible for the sleep method to change the thread state to TIMED_WAITING.

isAlive

The isAlive method is used to check if a thread is still alive. It is a native method, but it is not static, meaning that it must be called by an instance of the thread.

In fact, you can think about why it is not a static method, because static methods usually operate on the thread that is currently executing. Since it is “currently executing”, it must be Alive, so it does not make sense to call a static method.

/**
 * Tests if this thread is alive. A thread is alive if it has
 * been started and has not yet died.
 *
 * @return  <code>true</code> if this thread is alive;
 *          <code>false</code> otherwise.
 */
public final native boolean isAlive();
Copy the code

join

WAITING (TIMED_WAITING); WAITING (TIMED_WAITING);

/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
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; }}}Copy the code

The start of the source code comment tells us what the join method does:

Waits at most {@code millis} milliseconds for this thread to die. A timeout of {@code 0} means to wait forever. That is, the method waits for this thread to terminate, at most for the specified time, or forever if the specified time is 0.

Here are two things to be clear about:

  1. Who is waiting for this thread to terminate?
  2. Which thread does this thread refer to?

To illustrate, let’s go straight to an example:

public class JoinMethodTest {

    private static void printWithThread(String content) {
        System.out.println("[" + Thread.currentThread().getName() + "Thread]:" + content);
    }

    public static void main(String[] args) {

        printWithThread("Start executing main method");

        Thread myThread = new Thread(() -> {
            printWithThread("I'm in the run method of a custom thread.");
            printWithThread("I'm going to rest for a second and free up the CPU for another thread.");
            try {
                Thread.sleep(1000);
                printWithThread("Rested for 1 second and regained CPU");
                printWithThread("I rested up and quit right away.");
            } catch(InterruptedException e) { e.printStackTrace(); }});try {
            myThread.start();
            printWithThread("I'm inside main, and I'm waiting for the next thread to finish before I can continue.");
            myThread.join();
            printWithThread("I'm inside main and I'm going to exit.");
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

In the above example, we called mythread.join () from the main method. Note that the code above has two threads, one is the thread executing the main method and the other is our custom myThread thread, so the answers to the above two questions are:

  1. The main thread is waiting for this thread to terminate because we called mythread.join () in the main method.
  2. This Thread thread refers to the myThread thread because we called the Join method on the myThread object.

The result of this code is:

[main Thread]: I'm in main, and I'm waiting for the next Thread to finish before I can continue0Thread: I use Thread- in the custom Thread's run method0Thread]: I'm going to take a break1Second, and free up the CPU for another Thread0Thread]: Has rested1In seconds, the CPU [Thread-] has been regained0[main thread]: I'm inside main and I'm about to exit.Copy the code

The main Thread must wait until it finishes executing, even though the myThread (thread-0) has surrendered the CPU. Let the main thread wait at most 0.5 seconds; change mythread.join () to mythread.join (500); , the results are as follows:

[main Thread]: I'm in main, and I'm waiting for the next Thread to finish before I can continue0Thread: I use Thread- in the custom Thread's run method0Thread]: I'm going to take a break1[Thread-]: I'm in main and I'm about to exit0Thread]: Has rested1In seconds, the CPU [Thread-] has been regained0Thread]: I rested and quit right awayCopy the code

As we can see, since the main thread waits for myThread for up to 0.5 seconds, it does not wait until the myThread is dormant for a second and continues to execute, and then myThread grabs CPU resources and continues to execute.

Join (0) : join(0)

public final synchronized void join(long millis) throws InterruptedException {
    ...
    if (millis == 0) {
        while (isAlive()) {
            wait(0); }}else{... }... }Copy the code

This is a spin operation. Note that the isAlive and wait(0) methods are Thread instance methods, in this case myThread. Thread is a Thread class, but only in its native method. In Java, all classes inherit from Object, so Thread inherits Object’s wait method. MyThread, as an instance of Thread class, also has its wait method.

The wait method must hold the monitor lock and be called in a synchronized block of code. Here we examine the join method and find that it is indeed a non-static method modified by the synchronized keyword. So it uses the monitor lock (this) of the current object instance.

It seems to be getting complicated, so let’s get a grip from beginning to end (** pay attention! Knock on the blackboard! This part is tricky! * *) :

  1. First, let’s make it clear that there are two threads involved, the main Thread and our custom myThread Thread (thread-0 in our example).
  2. We called myThread.join() in the main method, which is executed by the main thread, so the “current thread” executing myThread.join() is the main thread.
  3. The Join method is a synchronous method that uses the object lock (this lock), the monitor object with which the myThread object is associated.
  4. The main thread must first acquire the monitor lock of the JOIN method before it can access the synchronized code block.
  5. When the main thread enters the block, it checks to see if the myThread isAlive first. Note that isAlive is the method of the myThread, which checks to see if the myThread isAlive, not the current thread (the current thread is the thread executing isAlive, i.e. the main thread).
  6. If myThread is still alive, the main thread waits indefinitely, relinquishes the monitor lock, and enters a WAITING state.
  7. When the main thread wakes up from WAITING (through notify, notifyAll, or a false wake), it continues to compete for the monitor lock, and when it successfully obtains the monitor lock, it resumes running from where wait was called. Since the wait method is in a while loop, it continues to check for the survival of the myThread thread and continues to suspend the wait if it still hasn’t terminated.
  8. As you can see, the only way to exit this “spin” state is for the myThread thread to terminate (or have an interrupt exception thrown).

If no one calls notify or notifyAll, and no false wake state occurs, the main thread is always suspended by wait(0). There is no chance that the myThread thread will survive. If myThread terminates, you cannot exit.

This is explained in the notes:

As a thread terminates the {@code this.notifyAll} method is invoked. NotifyAll is called when myThread terminates. NotifyAll is called when myThread terminates. The main thread is the thread waiting on the monitor lock. So when myThread finishes running, the main thread is woken up from the wait method.

In addition, the note added a sentence:

It is recommended that applications not use {@code wait}, {@code notify}, or {@code notifyAll} on {@code Thread} instances. This recommendation is very necessary, as for why, let us leave it as a question <()>

It is important to distinguish between the thread that executes the code and the thread class that the method belongs to.

For example, in the example above:

  • Mythread.join () is the myThread object method, but it is executed by the main thread;
  • IsAlive () is a method of the myThread object, but it is executed by the main thread and checks whether the myThread isAlive
  • Wait (0) is a method of the myThread object, executed by the main thread, which suspends on the monitor represented by the myThread object.

The most important distinction here is the distinction between “myThread object” and “myThread thread”. Sometimes the myThread object represents the myThread thread. For example, the isAlive method of the myThread object checks whether the thread it represents isAlive. MyThread objects are ordinary Java objects whose methods are usually executed by other threads (such as the main thread in the example above). For custom threads (such as the myThread thread in the example above), the only method that can be executed by itself is the run method passed in.

Going back to the above example, we can see from the above analysis that the join(0) method implements a certain degree of thread synchronization, i.e. the current thread cannot continue until the thread represented by the thread object to which the JOIN method belongs terminates, otherwise it will remain suspended.

Join (0) can be dangerous because if myThread is suspended because it can’t get a resource and the main thread is waiting for myThread to terminate, the program will never stop and cannot terminate.

public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0; .if (millis == 0) {... }else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break; } wait(delay); now = System.currentTimeMillis() - base; }}}Copy the code

Different from indefinite wait, timed wait only waits for the specified time, if the specified time is up to jump out of the loop directly, the wai method is also the version of timed wait, after the timed time, the main thread will be automatically woken up. The above code is self-explanatory, so I won’t repeat it.

Let’s look at the other two versions of the join method:

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

public final synchronized void join(long millis, int nanos) throws InterruptedException {

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

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException("nanosecond timeout value out of range");
    }

    if (nanos >= 500000|| (nanos ! =0 && millis == 0)) {
        millis++;
    }

    join(millis);
}
Copy the code

The other two versions call the first version of our analysis, which is similar to the wait and sleep methods. As for why wait and join methods provide no arguments and sleep methods do not, I think it is to maintain semantic consistency:

Wait () and join() are equivalent to wait(0) and join(0), respectively. Sleep (0) does not represent an indefinite wait, so the sleep method has no no-parameter form to prevent semantic confusion. Other than that, the implementation of these three methods in the two-parameter version XXX(long Millis, int nanos) is pretty much the same.

One last thing to note is that in the join method, we only call isAlive to check whether the thread isAlive, but we do not start the thread. That is, if we want to wait for myThread to finish executing, Mythread.start () must be called before mythread.join (). Otherwise, the join method will exit if isAlive is false and myThread will not be executed. You can comment out myThread.start() and run it yourself.