Make writing a habit together! This is my first day to participate in the “Gold Digging Day New Plan · April More text challenge”, click to see the details of the activity.

The usage of multithreading should also be considered as a difficult point in the development process of Java language. This series focuses on some API uses in multithreading. Note that the concept of multi-threading will not be introduced here. If you are not familiar with the basic concepts of what multi-threading is, it is recommended to learn some concepts first. This article will start directly with the creation of multithreading.

Thread and Runnable interfaces

Thread class represents the abstraction of Thread. Since Thread startup and execution must deal with the underlying operating system, there are many native modified local methods in Thread class. This class also contains many operations on threads. Constructor methods are used to create threads, start methods are used to start threads, interrupt, sleep, join,yield, etc.

Runnable itself is an interface that contains a method run that returns no value. The interface abstracts the Thread task, that is, what the Thread is doing. It simply represents the task and has no ability to start a Thread.

Thread also implements the Runnable interface. This class also has run methods, so we can specify Thread tasks by inheriting the Thread class to override the run method. In the argument constructor for Thread, we can also specify Thread tasks by passing a Runnable externally. Let’s demonstrate two different ways to do multithreading.

Starting multithreading is divided into three steps:

  • Creating a Thread (Thread class and its subclasses)
  • Specifying tasks (Thread run or Runnable run)
  • Starting a Thread (Thread start)

Code cases

2.1 inheritance Thread

The Thread class already implements the Runnable interface, so we can inject Thread tasks when overriding the run method by inheriting Thread.

package com.lsqingfeng.action.knowledge.multithread.thread;

/ * * *@className: ThreadDemo2
 * @description:
 * @author: sh.Liu
 * @date: the 2022-04-06 glorifying the * /
public class ThreadDemo2 extends Thread{

    public void run(a){
        System.out.println(Thread.currentThread().getName() + ": I am Thread Task");
    }


    public static void main(String[] args) throws InterruptedException {
        ThreadDemo2 t1 = new ThreadDemo2();
        ThreadDemo2 t2 = new ThreadDemo2();

        t1.start();
        t2.start();

// t1.join();
// t2.join();

        System.out.println("End of execution"); }}Copy the code

In the above code, ThreadDemo2 inherits the Thread class, so ThreadDemo2 represents a Thread. The run method is reassigned to the Thread, and the two Thread objects are started by calling the start() method. So in the current program, there are three threads, one for T1, one for T2, and one for the main thread. The result is as follows:

Thread-1: I am Thread Task Thread0: I am Thread TaskCopy the code

You can use the join method if you want the main thread to end the print execution after the other threads have finished.

Of course, inheriting a class and overwriting its methods, we can also use anonymous inner classes to simplify our code.

new Thread(){
    public void run(a){
        System.out.println("I'm an implementation of the run method in a subclass of Thread.");
    }
}.start();
Copy the code

If there are some students who do not understand, it means that you do not understand this part of the knowledge of anonymous inner class enough, so hurry up to supplement it. Also note that since the Run method in the Thread class is not a functional interface, lambda expressions cannot be used here.

In this way, Thread subclasses both contain Thread objects and act as Thread tasks, which means that the first two steps of our multithreading are all done in one class. Now let’s look at the second one.

2.2 implement Runnable

In the Thread constructor, we can pass in the Runnable interface, that is, we can pass in Thread tasks by constructing methods.

You can also pass the thread name along with the task:

Runnable itself is an interface, and we must specify its implementation.

package com.lsqingfeng.action.knowledge.multithread.thread;

/ * * *@className: ThreadDemo3
 * @description:
 * @author: sh.Liu
 * @date: the 2022-04-06 14:05 * /
public class ThreadDemo3 implements Runnable{

    @Override
    public void run(a) {
        System.out.println(Thread.currentThread().getName() + "I am Thread Task");
    }

    public static void main(String[] args) {
        R1 is a Thread task. A Thread task cannot start a Thread and must rely on Thread
        Runnable r1 = new ThreadDemo3();

        // Create a thread and pass in its task R1 and thread name
        Thread t1 = new Thread(r1, "thread-01");

        // Create another thread
        Thread t2 = new Thread(r1, "th-02");

        // Start separately: When a thread is started, the run method in the thread task is automatically executedt1.start(); t2.start(); }}Copy the code

Execution Result:

thread-01I am Thread Task

th-02I am Thread Task

In the above code, we treat the Runnable subclass (implementation class) as the Thread’s task, creating the Thread, passing the Thread’s task in through the constructor, or calling the Thread’s start method to start the Thread. Once again, Runnable cannot start a thread. It has only one run method. Only the Thread class and its subclasses can start a Thread, which automatically calls the run() method in the Thread task.

We can also simplify this by using anonymous inner classes:

 // Anonymous inner class:
Runnable r2 = new Runnable() {
    @Override
    public void run(a) {
        System.out.println("I'm the new thread task."); }};new Thread(r2,"th-03").start();

// Write together:
new Thread(new Runnable() {
    @Override
    public void run(a) {
        System.out.println("I'm the new Thread task 2"); }},"th-04").start();

// Use lambda expressions to simplify: because there is only one interface in Runnable that needs to be implemented, which is a functional interface
new Thread(()->{
    System.out.println("I'm new Thread task 3");
},"th-05").start();
Copy the code

In both cases, the second approach is generally considered to be more elegant and consistent with the single-responsibility design principle, where a thread is a thread and a task is a task, each doing its job and being a little clearer.

Third, source code analysis

We have mentioned two ways to implement multithreading. The main way is that Thread tasks are passed in different ways. One is to directly rewrite the Thread subclass, and the other is to pass in the constructor method. So if my thread object is passed a thread task in both ways, which thread task will execute? Let’s write a case first. Let me make it clear:

package com.lsqingfeng.action.knowledge.multithread.thread;

/ * * *@className: ThreadDemo4
 * @description:
 * @author: sh.Liu
 * @date: the 2022-04-06 away * /
public class ThreadDemo4 extends Thread{

    public ThreadDemo4(a){}public ThreadDemo4(Runnable r){
        super(r);
    }

    public void run(a){
        System.out.println("Run method in Thread class");
    }

    public static void main(String[] args) {
        // To summarize the previous approach:
        Method 1 starts the Thread by subclass Thread
        new ThreadDemo4().start();

        // Method 2: new Thread, pass the Runnable interface:
        new Thread(new RunnableDemo()).start();


        // The constructor must be defined in the subclass because the call is displayed
        new ThreadDemo4(newRunnableDemo()).start(); }}class RunnableDemo implements Runnable{

    @Override
    public void run(a) {
        System.out.println("Run method in Runnable"); }}Copy the code

Print result:

The run method of the Thread class Runnable

We can see from the results that the run method in a subclass of Thread works when we mix the two methods. Why is that? This needs to understand the source code.

The Thread class implements the Runnable interface, so there is a run method in the class itself.

public
class Thread implements Runnable {
    @Override
    public void run(a) {
        if(target ! =null) { target.run(); }}}Copy the code

Target is a Thread member variable Runnable:

Thread implements not only the Runnable interface, but also the Runnable as a member variable. The Runnable passed in through the parameter constructor is actually assigned to the target variable.

Call the init method:

The assignment to target is done in the init method.

When we start a thread by calling the start() method, the run method is automatically called at the bottom of the thread. This part is not reflected in the code. It is the native method called, so you can’t see the source code.

So when we start the thread the second way:

New Thread(new RunnableDemo()).start();Copy the code

The target assignment is complete, so when we call the run method:

 @Override
public void run(a) {
    if(target ! =null) { target.run(); }}Copy the code

Since target is no longer empty, the run method in Runnable passed in from the outside is called.

When subclassing Thread, we overwrote the run method and called the subclass’s own run method. The target code will not be executed. This explains why the third case prints the Run method in Thread.

This question is easier to understand if carefully analyzed, but sometimes interviews are combined with anonymous internal questions, such as:

public static void main(String[] args) {
      new Thread(
              ()->System.out.println("aaa")
      ){
          public void run(a){
              System.out.println("bbbb");
          }
      }.start();

}
Copy the code

What is the result of this code execution: BBBB, if not clear, study the anonymous inner class, read the above several times, should understand.

Finally, let’s look at the start method:

    public synchronized void start(a) {
        /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */
        if(threadStatus ! =0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if(! started) { group.threadStartFailed(this); }}catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */}}}private native void start0(a);
Copy the code

The start method mainly sets several states, and then calls the native method start0() to enable multi-threading. Starting thread is the interface of the operating system, so it is necessary to call the native method to implement it. So when anyone asks the difference between the start() and run() methods, it should be clear that start() starts the thread and automatically calls run(). Calling run() alone does not start a new thread.

Okay, so that’s it for today, maybe next time we’ll talk about thread pools.