“This is the second day of my participation in the First Challenge 2022.

1. The opening words

When you think of concurrent programming, the first things that come to mind are threads, multithreading, JUC, Thread pools, ThreadLocal, and so on. It is true that concurrent programming is an integral part of Java programming, and mastering the core techniques of concurrent programming will be a great weapon in a job interview. Today we are going to talk about how Thread, the cornerstone of concurrent programming, works.

In fact, when I recall the core API of The Thread class and the corresponding Thread state transition relationship, I always feel a little vague, hence this article. The core topic of this article is the Thread class, which leads to a number of topics, such as processes and threads, Thread state and lifecycle, the use of the Thread API, and more.

2. Processes and threads

First, it is necessary to introduce processes and threads, and the differences and relationships between them.

Processes are the basic unit of resources allocated by the operating system. For example, we start a JVM process when we start a main method.

Threads, on the other hand, are smaller than processes and are the basic unit of CPU allocation (because that’s what’s really running). For example, starting a main method is a thread that belongs to the JVM process, called the main thread. A process can have one or more threads, and each thread in the same process shares the memory space of the process.

The difference between a process and a thread is as follows:

  • Processes are the smallest unit of resources allocated by the operating system, while threads are the smallest unit of CPU allocation (program execution)
  • A process consists of one or more threads, which are different execution paths of code in a process
  • Processes are independent of each other, but threads in the same process share the program’s memory space
  • Scheduling and switching: Thread context switching is much faster than process context switching

3. Common API of Thread

Since the author knows little about the core API of Thread class and its principle, this section mainly introduces the usage, significance and influence of the core API of Thread class on Thread state.

3.1 Creating a Threading task

Before calling the Thread API, you need to create the Thread object, which is as simple as new Thread(). But in fact, if you create a thread with new and do nothing else, the thread will not execute any business logic.

So we need another way to specify the business logic that threads execute, which begs the question: how many forms are there to create threaded tasks?

In general, we think of three forms: inheriting the Thread class, implementing the Runnable interface, and implementing the Callable interface, as described below.

3.1.1 Inheriting Thread

Create a class that inherits Thread and overrides the run method, which specifies the business logic to be executed by the Thread. The class can then be instantiated directly when the thread is created, and the override run method logic will be executed automatically after the thread is started.

public class CreateThreadByThread extends Thread {

    @Override
    public void run(a) {
        System.out.println("CreateThreadByRunnable#run, custom business logic");
    }

    public static void main(String[] args) {
        Thread thread = newCreateThreadByThread(); thread.start(); }}/ / outputCreateThreadByRunnable#run, custom business logicCopy the code

3.1.2 Implementing the Runnable Interface

Create a class that implements the Runnable interface and overwrites the Run method. Then create a Thread class and pass the object that implements the Runnable interface as an input to the Thread constructor. After starting the Thread object, the program executes the Runnable object’s run method.

public class CreateThreadByRunnable implements Runnable {

    @Override
    public void run(a) {
        System.out.println("CreateThreadByRunnable#run, custom business logic");
    }

    public static void main(String[] args) {
        Runnable runnable = new CreateThreadByRunnable();
        // Pass the Runnable object as an input to the Thread class constructor
        Thread thread = newThread(runnable); thread.start(); }}Copy the code

Although these two forms inherit the Thread class and implement the Runnable interface, there is no essential difference if you look at the source code.

Starting with the form of the Thread class, which needs to override the run method, let’s see what the default content of Thread#run looks like:

// Thread#run
@Override
public void run(a) {
  if(target ! =null) { target.run(); }}// Thread#target
/* What will be run. */
private Runnable target;
Copy the code

In fact, the Thread#run method is a run method that overrides the Runnable interface. The logic is to execute the run method when the private member of the Runnable type is not empty.

In the form of the Runnable interface, after creating an object of type Runnable, we pass it as an input parameter to the constructor of the Thread class.

// Thread#Thread(java.lang.Runnable)
public Thread(Runnable target) {
  init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
  // omit other code
  
  this.target = target;

  // omit other code
}
Copy the code

You can see that in the overloaded constructor of Thread, the object of type Runnable passed in is assigned to the Target private member variable of Thread. Again, relate to the Thread#run method we just mentioned: the run method is executed when the private member variable of type Runnable is not empty.

Isn’t that just a new skin?

Therefore, implementing the Runnable interface is not fundamentally different from inheriting the Thread class. They are based on overwriting the Run method to implement the tasks that the Thread needs to perform.

3.1.3 Implement the Callable interface

The Callable interface is a class introduced in JDK1.5, which is more powerful than Runnable. The most important feature is that Callable allows a value to be returned. Second, it supports generics, and it allows an exception to be thrown by outer code.

public class CreateThreadByCallable implements Callable<Integer> {

    public static void main(String[] args) {
        CreateThreadByCallable callable = new CreateThreadByCallable();
        FutureTask<Integer> future = new FutureTask<>(callable);
        // Create a thread and start it
        Thread thread = new Thread(future);
        thread.start();

        Integer integer = null;
        try {
            integer = future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("FutureTask returns content:" + integer);

    }

    @Override
    public Integer call(a) throws Exception {
        System.out.println("CreateThreadByCallable#call, custom business logic, returns 1");
        return 1; }}Copy the code

It is worth noting that we need to use the Callale interface based on the FutureTask class, which provides return values, exceptions, and generics.

Digging into FutureTask’s constructor and its internal methods, I discovered something new.

// FutureTask#FutureTask(java.util.concurrent.Callable<V>)
public FutureTask(Callable<V> callable) {
  if (callable == null)
    throw new NullPointerException();
  this.callable = callable;
  this.state = NEW;
}
Copy the code

First, in the FutureTask constructor, the Callable object is assigned to a private member variable of type Callable of the FutureTask class. The Thread overload constructor is consistent with the Runnable interface. FutureTask implements the Runnable interface.

The FutureTask class implements the RunnableFuture interface
public class FutureTask<V> implements RunnableFuture<V> {}

// The RunnableFuture interface inherits the Runnable interface
public interface RunnableFuture<V> extends Runnable.Future<V> {}
Copy the code

So the FutureTask object we construct is passed into the Thread class as a Runnable, which executes the run method inside The FutureTask when the Thread starts. Let’s look at the FutureTask#run method.

// java.util.concurrent.FutureTask#run
public void run(a) {
  if(state ! = NEW || ! UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
    return;
  try {
    Callable<V> c = callable;
    if(c ! =null && state == NEW) {
      V result;
      boolean ran;
      try {
        // Execute the call method of the callable attribute to get the return value
        result = c.call();
        ran = true;
      } catch (Throwable ex) {
        result = null;
        ran = false;
        setException(ex);
      }
      if (ran)
        // If completed, the returned value is assigned to the outcome property (returned in the FutureTask#get method)set(result); }}finally {
    runner = null;
    int s = state;
    if(s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); }}// java.util.concurrent.FutureTask#set
protected void set(V v) {
  if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    outcome = v;
    UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final statefinishCompletion(); }}Copy the code

As you can see, in the FutureTask#run method, you actually execute the Callable#call method. So, in the form of the Callable interface, Thread will still execute Thread#run, which is overridden by FutureTask’s run method. Calling the Callable#call method is internal predefined logic for FutureTask#run.

3.1.4 Method of creating a thread

Through the above three forms of creating thread tasks and exploring their source code, we can see that either form is ultimately implemented in the form of overwriting Thread#run.

Here’s an aside: How many ways can you create a thread?

This question is often misunderstood on the web, with the majority of opinions suggesting that threads can be created by inheriting the Thread class, implementing the Runnable interface, and implementing the Callable interface. In fact, what I described above is “the form of creating a thread task,” and I want to highlight not the method of creating a thread but the method of creating a thread to execute a task.

As you can see from the example above, even with the latter two forms (implementing the Runnable interface and implementing the Callable interface), you still end up creating a Thread with a new Thread, but changing the Thread’s behavior by passing in a Runnable object.

So there is only one way to create a Thread: new Thread().

3.2 start

Thread#start is arguably the most common method of Thread class. This method is used to start Thread execution.

After calling the start method of the newly created thread, the thread will be converted from the NEW state to the RUNNABLE state, and the CPU will allocate the time slice to the thread at the appropriate time to actually execute the thread’s business method.

It is important to note that a thread can only be called once start method, otherwise will be thrown IllegalThreadStateException is unusual, the reason is in Thread# start method, first of all make thread state.

// java.lang.Thread#start
public synchronized void start(a) {
	// If the thread state is not NEW, raise an exception
  Buta pile cannot be called any more than A pile of facts. Buta pile cannot be called any more than A pile of facts. // A zero status value buta pile to state "NEW".
  if(threadStatus ! =0)
    throw new IllegalThreadStateException();

  // omit other logic...
}
Copy the code

As you can see, when the state of the thread is not a NEW state, calls its start method again, will be thrown IllegalThreadStateException anomalies. That is, once a thread has finished executing, it cannot be restarted.

3.3 the join

The Thread#join method is used to wait for the thread to finish executing. In JDK1.8, this method has three overloaded methods, and the other two overloaded methods have timeout values set:

Thread#join calls thread B’s join method from thread A, and thread A waits for thread B to finish executing. In this process, the state of thread A will change from RUNNABLE to WAITING, and the state of thread A will change to RUNNABLE after thread B completes execution. This conclusion can be verified by the following example:

public static void main(String[] args) throws InterruptedException {
  Thread thread1 = new Thread(() -> {
    System.out.println("thread1 is running");
    try {
      // To see the effect, set the sleep time to longer
      Thread.sleep(50000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("thread1 is over");
  });

  thread1.start();
  thread1.join();
}
Copy the code

Thread1 is running, thread1 enters sleep, and thread1 is over. Thread1. Join () thread is in WAITING state.

In the case of Thread#join, the state of the thread calling the join method on a thread object will change to TIMED_WAITING if the timeout is set.

In addition, there is another question worth considering: after the current thread calls the join method of another thread, if the other thread attempts to acquire the lock already held by the current thread, will it succeed? Let’s do an experiment.

private static String str = "123";

private static void testJoinLock(a) throws InterruptedException {
  // the main program occupies STR resources first
  synchronized (str) {
    Thread thread1 = new Thread(() -> {
      System.out.println("thread1 is running");
      // The child thread attempts to occupy the STR resource
      synchronized (str) {
        System.out.println("thread1 is get str lock");
      }
      System.out.println("thread1 is over"); }); thread1.start(); thread1.join(); }}Copy the code

The main thread first locks the shared resource STR variable and then instantiates a child thread that also tries to lock STR. Run the program and observe that the final output is thread1 is running and the program has not terminated.

Guessing that the child thread might be blocked, open JConsole as shown below:

Sure enough, the thread-0 Thread was observed to be BLOCKED and the resource owner was Main, that is, the Thread was BLOCKED by the main Thread.

Therefore, when thread A enters the WAITING state by calling thread B’s join method, the lock resources it already holds will not be released.

3.4 yield

The Thread#yield method frees up the time slice and lets the CPU select the thread to execute again. The CPU may select the thread that abandoned the timeslice to execute.

Note that the Thread#yield method does not release lock resources that are already held.

3.5 interrupt

The Thread#interrupt method interrupts a thread. This method requests the termination of the current thread. Note that this method simply sends a termination message to the current thread and sets the interrupt flag.

There are two more similar methods: Thread#interrupted, Thread#isInterrupted.

The Thread#interrupt method checks to see if the thread is interrupted and clears the interrupt flag.

The Thread#isInterrupted method checks if the thread has been interrupted, but does not clear the interrupt flag bit.

It is worth noting that when the Thread#interrupt method of a thread is called, If the current thread is in TIMED_WAITING or WAITING state (such as Object#wait, Thread#join, Thread#sleep, or the corresponding overloaded thread), InterruptedException is thrown, causing the thread to enter the TERMINATED state directly.

public static void main(String[] args) {
  Thread thread1 = new Thread(() -> {
    System.out.println("thread1 is running");
    try {
      // To see the effect, set the sleep time to longer
      Thread.sleep(50000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("thread1 is over");
  });

  thread1.start();
  // Interrupt the thread
  thread1.interrupt();
}
Copy the code

Execute this method, and the output is:

thread1 is running
thread1 is over
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at io.walkers.planes.pandora.jdk.thread.usage.InterruptMethod.lambda$main$0(InterruptMethod.java:16)
	at java.lang.Thread.run(Thread.java:748)
Copy the code

As you can see, the program throws InterruptedException.

4. Thread status

In the Java language, threads are abstracted into the Thread class, and within the Thread class there is a State enumeration class that describes the various states of a Thread.

// java.lang.Thread.State
public enum State {
  NEW,
  RUNNABLE,
  BLOCKED,
  WAITING,
  TIMED_WAITING,
  TERMINATED;
}
Copy the code

The comments in the JDK source code describe thread status as follows:

  • NEW: An unstarted thread is in this state
  • RUNNABLE: Threads executing in the JVM are in this state
  • BLOCKED: A thread that is BLOCKED while waiting for a lock
  • WAITING: the state in which a thread is WAITING indefinitely for another thread to perform a particular operation
  • TIMED_WAITING: A thread in this state that is waiting for another thread to perform an operation within the specified waiting time
  • TERMINATED: The TERMINATED thread is in this state

I’ll use code to simulate a thread in each state and illustrate how it can enter that state.

4.1 NEW

The unstarted Thread is in the NEW state, which is easy to simulate. When I create a NEW Thread object, the Thread is in the NEW state. The simulation code is as follows:

public static void main(String[] args) {
  Thread thread = new Thread();
  System.out.println("Thread state is: " + thread.getState());
}

// Program output content
Thread state is: NEW
Copy the code

So when you create a thread object, the thread’s state is NEW.

4.2 a RUNNABLE

A thread executing in the JVM is in a RUNNABLE state, which means that when a thread’s start method is called, it waits for the CPU to allocate time to the thread.

public static void main(String[] args) {
  Thread thread = new Thread(() -> System.out.println("Thread state is: " + Thread.currentThread().getState()));
  thread.start();
}

// Program output content
Thread state is: RUNNABLE
Copy the code

So if a thread’s start method is called and no exception is thrown, the thread will enter the RUNNABLE state.

Here it is important to note that if for the NEW state of the thread calls the start method, it will be thrown IllegalThreadStateException is unusual, because the source code is as follows:

// java.lang.Thread#start
public synchronized void start(a) {
	// If the thread state is not NEW, raise an exception
  Buta pile cannot be called any more than A pile of facts. Buta pile cannot be called any more than A pile of facts. // A zero status value buta pile to state "NEW".
  if(threadStatus ! =0)
    throw new IllegalThreadStateException();

  // omit other logic...
}
Copy the code

In addition, there are several other situations in which a RUNNABLE state can occur:

  • A thread in the BLOCKED state enters the RUNNABLE state when the lock is successfully acquired
  • A thread in WAITING/TIMED_WAITING state due to call sleep or join will enter the RUNNABLE state when the timeout period expires, or when call Object#notify or Object#notifyAll
  • A RUNNABLE thread returns to the RUNNABLE state by invoking the yield method

4.3 BLOCKED

Thread will be BLOCKED in the BLOCKED state due to waiting for the lock, to simulate the condition of thread need to introduce a Shared resource, and the second thread, thread 1 holds the lock up and resources first, and then start the thread 2, when the thread 2 attempts to acquire the lock resources, share resources have been found by thread 1, then enter the blocking state. The simulation code is as follows:

public class StateBlocked {
		// Share resources
    private static String str = "lock";

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (str) {
                System.out.println("Thread1 get lock");
                // Prevent thread thread1 from releasing the STR lock resource
                try {
                    Thread.sleep(10000);
                } catch(InterruptedException e) { e.printStackTrace(); }}}); thread1.start();// Make sure thread1 gets the lock resource first
        Thread.sleep(1000);

        Thread thread2 = new Thread(() -> {
            synchronized (str) {
                System.out.println("Thread1 get lock"); }}); thread2.start();// Ensure that thread2 enters the synchronized block
        Thread.sleep(1000);
        System.out.println("Thread2 state is: "+ thread2.getState()); }}Copy the code

The output of the simulated code above is as follows:

Thread1 get lock
Thread2 state is: BLOCKED
Thread1 get lock
Copy the code

So a thread is BLOCKED when it attempts to acquire a lock by entering a synchronized block.

4.4 WAITING

A thread that is WAITING indefinitely for another thread to perform a particular operation is in a WAITING state. Start thread A and call Thread#join on thread B. Thread B is WAITING for thread A to complete.

public static void main(String[] args) throws InterruptedException {
  / / lock resources
  String str = "lock";
  Thread thread = new Thread(() -> {
    // Sleep 100s to allow enough time to view the thread status
    try {
      Thread.sleep(100000);
    } catch(InterruptedException e) { e.printStackTrace(); }}); thread.start();// The main thread waits for the thread to complete
  thread.join();
}
Copy the code

After executing this method, we need to open JConsole, find the corresponding process, and check its thread status as shown below:

The state of the main thread is WAITING.

So, if you call a thread’s join method (with no timeout specified), the caller enters a WAITING state.

Otherwise, calling a thread’s Object#wait (with no timeout specified) will cause the caller to enter a WAITING state.

4.5 TIMED_WAITING

A thread waiting for another thread to perform an operation within the specified waiting time is in TIMED_WAITING state. The only difference between TIMED_WAITING state and WAITING state is that the former state specifies the timeout time. After a slight change on the basis of the previous code, we can simulate TIMED_WAITING state. The simulation code is as follows:

public static void main(String[] args) throws InterruptedException {
  / / lock resources
  String str = "lock";
  Thread thread = new Thread(() -> {
    // Sleep 100s to allow enough time to view the thread status
    try {
      Thread.sleep(100000);
    } catch(InterruptedException e) { e.printStackTrace(); }}); thread.start();// The main thread waits for the completion of thread execution. The timeout period is set to 10 seconds
  thread.join(10000);
}
Copy the code

After executing this method, open JConsole, find the corresponding process, and check its thread status as shown below:

As you can see, the main thread is in TIMED_WAITING state.

So, by calling a thread’s join method (specifying the timeout), the caller enters a TIMED_WAITING state.

Otherwise, call a thread’s Object#wait and the caller will enter a WAITING state.

4.5 TERMINATED

The TERMINATED thread is in TERMINATED state. This state is the natural termination of a thread and is easy to simulate. The code is TERMINATED as follows:

public static void main(String[] args) throws InterruptedException {
  Thread thread = new Thread();
  thread.start();
  // Wait for the thread to complete
  thread.join();
  System.out.println("Thread state is: " + thread.getState());
}
Copy the code

So, when a thread terminates normally, it goes into TERMINATED state.

It is also TERMINATED when a thread exits with an exception, such as in WAITING/TIMED_WAITING by calling Thread#interrupt.

5. Thread state transition diagram

The above thread state transition relationship is summarized as the following figure:

6. Summary

This article first introduces the connection and difference between process and Thread, then describes the common API of Thread class and the form of creating Thread to perform tasks, then explains the Thread state in detail and makes examples, and finally summarizes the Thread state transition diagram.

Summary of a few small issues:

  • The relation and difference between process and thread
  • What are the forms of creating thread tasks?
  • How many ways can you create a thread?
  • What kinds of thread states are there?
  • Describe the thread state transition rules

7. Reference materials

  • The beauty of Concurrent programming in Java

Finally, this article is included in the Personal Speaker Knowledge Base: Back-end technology as I understand it, welcome to visit.