Thread class source code interpretation 2 -- thread state and common methods

Posted by john-formby on Mon, 07 Mar 2022 17:14:39 +0100

The source code of this article is based on jdk1 8 .

Thread state

In the Thread class, the Thread status is realized through the threadStatus property 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);
}

As can be seen from the source code, threads have six states, and their state transition relationship is shown in the following figure:

It is worth mentioning that from the definition of state, the RUNNABLE state includes the two states we usually call running and ready.

common method

currentThread

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

It can be seen that it is a static method and a native method, which returns the thread currently executing.

Thinking students may want to ask, now we all have multi-core CPUs. Multiple threads can run on different CPU cores at the same time. There are multiple threads currently executing. Which one is returned?

In fact, "currently executing thread" here refers to the thread currently executing this code.

As we know, thread is the smallest unit of CPU scheduling, and any piece of code must be executed by one thread, so the method returns the executing thread The thread of currentthread, for example:

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

Output:

main

We know that when a java program is started, a thread will run immediately, which is usually called the main thread. The main thread will execute the java entry method main method, so it is currently executing thread The thread of the currentthread () method is the main thread.

sleep

When talking about the sleep method, the two most frequently asked questions are:

  • Thread.sleep() and thread currentThread(). What's the difference between sleep()?
  • Thread.sleep() and object What's the difference between wait()?

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

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

It can be seen that the sleep method is also a static method and a native method. It can be seen from the annotation Causes the currently executing thread to sleep that it acts on the currently executing thread, so we can answer the above question:

Thread.sleep() and thread currentThread(). No difference between sleep

If there is any difference between them, one is to call static methods directly with classes, and the other is to call static methods with instances of classes

In addition, there is another very important sentence in the above note:

The thread does not lose ownership of any monitors.

In other words, although the sleep function makes the current thread give up the CPU, the current thread still holds the monitor lock it has obtained, which is different from the wait method of giving up CPU resources 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);
}

This method adds a nanosecond delay parameter, but we can see from the source code that this additional nanosecond delay is useless. Finally, the function calls the above single parameter native sleep method. The delay is still in the millisecond level. The additional parameters can increase the delay of the current millisecond level by 1 millisecond at most
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);
}

What about? Isn't it very similar? There is only a slight difference between the two in the carry from nanosecond to millisecond. I guess this inconsistency is caused by historical reasons.

In addition, it is worth mentioning that the wait() method with and without parameters calls wait(0), which means waiting indefinitely, while sleep does not have an parameterless version. What does sleep(0) represent?

This is not mentioned in the source code, but by guessing the definition of the sleep method, we know that it gives up the CPU 0 milliseconds, which sounds meaningless, but actually calls thread The current thread of sleep (0) is indeed "frozen", giving other threads a chance to execute first. In other words, the current thread will release some unused time slices for use by other threads or processes, which is equivalent to a yield action, which looks very similar to the yield method to be mentioned below.

yield

Since the sleep(0) method is mentioned above, we have to mention the yield method:

/**
 * 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 hint to the scheduler that the current thread is will to yield its current use of a processor The scheduler is free to ignore this hint. It is only a suggestion for the CPU. It tells the CPU that the current thread is willing to give up the CPU to other threads. Whether the CPU is adopted or not depends on the behavior of different manufacturers. It is possible that a thread just yields the CPU and then obtains the CPU immediately. In contrast, the sleep method will give up CPU resources and sleep for a specified time without participating in CPU competition

Therefore, calling the yield method will not cause the thread to exit the running state. At most, it will change the thread from running to ready,
However, the sleep method is possible to convert the thread state to TIMED_WAITING.

isAlive

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

In fact, you can think about why it is not a static method, because static methods generally act on the thread currently executing. Since it is "currently executing", it must be Alive, so it is meaningless to call as 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();

join

The join method is another method that can convert the thread state to WAITING or timed_ Like the wait method, WAITING has three versions. Let's look at them one by one:

/**
 * 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;
        }
    }
}

The function of the join method is explained at the beginning of this source code comment:

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 the termination of this thread for the specified time at most. If the specified time is 0, it will wait all the time.

Here are two questions to be clarified:

  • Who's waiting for this thread to terminate?
  • Which thread does this thread refer to?

For ease of illustration, let's take a direct look at 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 execution main method");

        Thread myThread = new Thread(() -> {
            printWithThread("I'm in the custom thread run Method inside");
            printWithThread("I'm going to rest for a second, And make way CPU For other threads.");
            try {
                Thread.sleep(1000);
                printWithThread("I've rested for a second, Again CPU");
                printWithThread("I have a good rest, Quit right away");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        try {
            myThread.start();
            printWithThread("I am here main Method inside, I can't continue until the following thread is executed.");
            myThread.join();
            printWithThread("I am here main Method inside, I'm quitting soon.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

In the above example, we call myThread. in the main method. Join(). Note that the above code has two threads, one is the thread executing the main method, and the other is our custom myThread thread. Therefore, the answers to the above two questions are:

  • The main thread is waiting for the termination of this thread, because we called myThread. in the main method. join()
  • this thread refers to the myThread thread, because we called the join method on the myThread object.

The execution result of the above code is:

[main thread ]: Start execution main method
[main thread ]: I am here main Method inside, I can't continue until the following thread is executed.
[Thread-0 thread ]: I'm in the custom thread run Method inside
[Thread-0 thread ]: I'm going to rest for a second, And make way CPU For other threads.
[Thread-0 thread ]: I've rested for a second, Again CPU
[Thread-0 thread ]: I have a good rest, Quit right away
[main thread ]: I am here main Method inside, I'm quitting soon.

It can be seen from the running results that although the myThread thread (i.e. Thread-0 thread) gives up the CPU halfway, the main thread must wait until its execution is completed before continuing to execute. Now we modify the code to make the main thread wait at most 0.5 seconds, that is, myThread Change join() to myThread join(500);, The results are as follows:

[main thread ]: Start execution main method
[main thread ]: I am here main Method inside, I can't continue until the following thread is executed.
[Thread-0 thread ]: I'm in the custom thread run Method inside
[Thread-0 thread ]: I'm going to rest for a second, And make way CPU For other threads.
[main thread ]: I am here main Method inside, I'm quitting soon.
[Thread-0 thread ]: I've rested for a second, Again CPU
[Thread-0 thread ]: I have a good rest, Quit right away

We can see that since the main thread waits for myThread for 0.5 seconds at most, it waits for one second after myThread sleeps and continues to execute. Then myThread grabs CPU resources and continues to run.

After having a perceptual understanding through Liezi, let's look at the source code. First, let's look at the part of join(0):

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

This is a spin operation. Note that both isAlive and wait(0) methods here are Thread instance methods. In the above example, it is the method of myThread. Although Thread is a Thread class, it is only special in its native method. In addition, it is a general java class, and all classes in java inherit from the Object class, Therefore, Thread class inherits the wait method of Object. As an instance of Thread class, myThread naturally has a wait method.

Before we mentioned the wait method, we mentioned that the execution of the wait method must get the monitor lock and must be invoked in the synchronous code block. Here we check that the join method finds that it is indeed modified by the synchronized keyword and is a non static method, so it makes use of the monitor lock (this) of the current object instance.

It seems to be getting complicated. Let's stroke it from beginning to end (pay attention! Knock on the blackboard! This paragraph is more winding!):

  • First of all, we need to make it clear that there are two threads involved here, one is the main thread and the other is our custom myThread thread (thread-0 in the example).
  • We called myThread. in the main method. Join(), the main method is executed by the main thread, so execute myThread The "current thread" in the line of join () is the main thread.
  • The join method is a synchronization method, which uses the object lock (this lock), that is, the monitor object associated with the myThread object.
  • The main thread must first get the monitor lock of the join method to enter the synchronous code block.
  • After the main thread enters the synchronization code block, it will first check whether the myThread thread is still alive. Note that isAlive here is the method of the myThread thread. It checks whether the myThread thread is still alive, not the current thread (the current thread is the thread executing the isAlive method, that is, the main thread).
  • If the myThread thread is still alive, (main thread) waits indefinitely, releases the monitor lock and enters the WAITING state.
  • When the main thread wakes up from the WAITING state (through notify, notifyAll or false wake-up), it will continue to compete for the monitor lock. When the monitor lock is successfully obtained, it will recover from the place where the wait is called and continue to run. Since the wait method is in the while loop, it will continue to check whether the myThread thread is alive. If it is still not terminated, it will continue to suspend the wait.
  • It can be seen that the only way to exit this "spin" state is for the myThread thread to terminate (or throw an interrupt exception).

Some careful students may have to ask: if no one calls notify or notifyAll and no false wake-up state occurs, won't the main thread be suspended by the wait(0) method all the time? Isn't there even a chance to check whether the myThread thread is alive? In this way, even if myThread terminates, you can't exit.

In fact, the notes explain this point:

As a thread terminates the {@code this.notifyAll} method is invoked.

As we know, the monitor lock of the wait(0) method is the myThread object (this). When myThread terminates execution, this NotifyAll will be called, so all threads waiting for this lock will be awakened, and the main thread is the thread waiting for this monitor lock. Therefore, when myThread runs, the main thread will be awakened from the wait method.

In addition, an additional sentence has been added to the note:

It is recommended that applications not use {@code wait}, {@code notify}, or {@code notifyAll} on {@code Thread} instances.

This recommendation is still very necessary. As for why, let's leave it for everyone to think about

However, let me be wordy here. We must distinguish between the thread executing the code and the thread represented by the thread class to which the method belongs!

For example, in the above example:

  • myThread.join() is the method of myThread object, but the main thread executes this method;
  • isAlive() is the method of the myThread object, but the main thread executes this method, and this method detects whether the myThread thread is alive
  • wait(0) is the method of the myThread object, but the main thread executes this method, which makes the main thread hang, but the main thread hangs on the monitor represented by the myThread object.

The most important thing here is to distinguish between "myThread object" and "myThread thread". Sometimes the myThread object represents the myThread thread. For example, the isAlive method of the myThread object detects whether the myThread thread it represents is alive. In fact, most of the time, the myThread object is an ordinary java object, The methods of this object are usually executed by other threads (such as the main thread in the above example). For our custom thread (such as the myThread thread above), the only method executed by itself is the run method passed in.

Returning to the above example, it can be seen from the above analysis that the join(0) method realizes thread synchronization to a certain extent, that is, the current thread can continue to execute only after the thread represented by the thread object to which the join method belongs terminates, otherwise it will hang and wait.

This also shows that using join(0) is very dangerous, because if the myThread thread is suspended because it cannot get resources, and the main thread is waiting for the myThread thread to terminate, the program will always stop there and cannot be terminated. Therefore, the limited time waiting version is provided in the source code:

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;
            }
        }
    }

Different from the indefinite wait, the time limited wait only waits for the specified time. If the specified time arrives, it will jump out of the loop directly. The wai method used is also the version of time limited wait. When the timing time arrives, the main thread will be awakened automatically. The above code is self explanatory, so I won't repeat it.

Next, 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);
}

It can be seen that the other two versions finally call the first version of our analysis, which is very similar to the wait method and sleep method. As for why the wait method and join method provide parameterless methods but the sleep method does not, I personally think it is to maintain semantic consistency:

wait() and join() are equivalent to wait(0) and join(0) respectively. They both represent indefinite waiting, while sleep(0) does not represent indefinite waiting. Therefore, the sleep method has no parameterless form to prevent semantic confusion. In addition to this, the implementation of these three methods in the two parameter version XXX(long millis, int nanos) is similar.

In addition, the last point worth noting is that in the join method, we only call the isAlive method to detect whether the thread is alive and do not start the thread. That is, if we want to realize the effect that the current thread waits for the execution of the myThread thread to complete, we must call myThread Join () before calling myThread.start() lets the thread run first. Otherwise, if the join method finds that isAlive is false, it will exit immediately, and the myThread thread will not be executed. You can call myThread Start() comment out, run for yourself and try.

Topics: Java Multithreading