Analyzing Thread and Runnable from source code

As we all know, multithreading is often used in advanced Java development. There are usually two ways to start a new thread:

  1. Define a subclass of Thread, and override the run method in the subclass.
  2. Define a class to implement the Runnable interface, rewrite the run method, and then execute it by creating a new thread.

The former is a completely independent Thread, which can run directly; the latter can not run directly, but must run through Thread thread. When more than one Thread is needed to complete the same task, the second method is generally recommended. Specific methods are introduced everywhere on the Internet, so we won't talk about them here. This article mainly analyzes the difference between the two from the source point of view.

First, let's look at the implementation of the Runnable interface:

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

As you can see from the annotation @ functional interface, this is a functional interface, that is, an interface with and only one abstract method. This interface is very simple. It only defines an abstract method, run(), which must be overridden by an implementation class.

Next, let's look at the Thread class definition:

public class Thread implements Runnable {
  .....
}

Our common thread class Thead is actually the implementation class of Runnable, which shows two problems:

1.Thread must override run() method;

2.Runnable can refer to Thread instance

When we need to use threads, we often define a subclass of the Thread class, rewrite the run() method, and then call the start() method to run the thread. What happened in the process? Let's explain it through source code analysis.

Let's skip the run() method and look at the start() method first:

    public synchronized void start() {
        /**
         * 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();

It can be seen that the core of start () method is to call start0(), which is a native method, belonging to the bottom implementation of Java virtual machine, and we can't see the source code. The essence of Java virtual machine bottom layer is to call the system call of operating system to start this thread. For example, under Linux, pthread_create must be called. We don't need to worry about this detail, but we start a thread anyway. What does this thread do? We can learn about it through the comment of start():

/**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */

The first sentence is obvious: causes this thread to begin execution; the Java virtual machine calls the < code > Run < / code > method of this thread. That is, when you execute the start() method, a new thread is created and the run() method is executed.

Let's take a look at the default run method in the Thread class:

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

For a very simple method, we will ignore the target object for the moment, and then we will talk about it later. Normally, target is null. In this case, run() does nothing. That is to say, you can directly create a Thread object and call start() to run it. This Thread will be created, executed and died, but nothing will be done!

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread();
        thread.start();
    }
}

Process finished with exit code 0

If you want the new Thread to perform the task you want to do, you have to define a subclass of Thread and override the run() method. Then the new Thread will execute the code in the run() method you wrote. This is also the most traditional Java Thread implementation.

//Self defined subclass
public class MyThread  extends Thread{

    //You must override the run() method, which completes what your thread has to do
    @Override
    public void run() {
        System.out.println("This is a child thread");
    }
}

//Test code
public class Main {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}

//Output result
This is a child thread

Process finished with exit code 0

This is the normal use of Thread. Creating multiple threads is similar to the above. Just define multiple objects. Rewrite the example as follows:

//Self defined subclass
public class MyThread  extends Thread{
    private int num = 5;

    @Override
    public void run() {
       while(num>0) {
           System.out.println("resume number:" + num--);
       }
    }
}

//Test code
public class Main {
    public static void main(String[] args) {
        Thread thread1 = new MyThread();
        thread1.start();

        Thread thread2 = new MyThread();
        thread2.start();

        Thread thread3 = new MyThread();
        thread3.start();
    }
}

//results of enforcement
resume number:5
resume number:4
resume number:3
resume number:2
resume number:1
resume number:5
resume number:4
resume number:3
resume number:2
resume number:1
resume number:5
resume number:4
resume number:3
resume number:2
resume number:1

It can be seen that the three threads are independent, which can be analyzed from the perspective of Java virtual machine.

The heap is a data area shared by all threads. We have created three MyThread objects in the heap

Three threads access their num members respectively, and they do not interfere with each other, so each thread will output 5 to 1.

OK, I've finished using Thread independently. Let's talk about the mixed use of Runnable and Thread.

We have seen the definition of Runnable above and know that there is only one abstract run() method in it. If we directly define a Runnable implementation class and rewrite the run() method, can this implementation class run directly???

The result is definitely negative. Let's take a look. First, rewrite MyThread to directly implement the Runnable interface.

public class MyThread  implements Runnable{
    private int num = 5;

    @Override
    public void run() {
       while(num>0) {
           System.out.println("resume number:" + num--);
       }
    }
}

Try starting the thread directly:

You will find that only run can call, there is no start() method at all!!! Because there is no such method in Runnable, and there is no such method in the class you defined. Through the above learning, you know that the start Thread must use the start() method, which is a low-level virtual machine implementation, and it is a Thread class method. Therefore, although your class implements the Runnable interface and rewrites the run() method, it cannot run. To run, you have to use the Thread class! At this point, we have to look at other initialization methods of the Thread class.

The Thread class has a construction method:

 public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

Remember that there is also a target in the run() method above? That's it! If you use a Runable implementation class to initialize when creating a Thread object, target will not be null. Let's continue to look at the source code, where init method is called:

  private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

Continue to trace. The called method is relatively long. Delete irrelevant code. The conclusion is as follows:

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        ......
        this.name = name;
        ......
        this.target = target;
        ......
    }

In fact, the target parameter passed in is used to initialize the target in the current Thread class. At this time, the target is not null. Look at the run() method again:

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

At this time, when the thread start() starts a new thread, target is no longer null, and the run() method will call run() defined in target. Therefore, the correct usage of Runnable is:

1. Define the implementation class MyThread of Runnable and override the run() method;

2. Create myThread object myThread;

3. Use myThread to initialize a thread object thread;

4. The thread object executes the start() method.

At this point, the thread is created to execute the run() method defined in the Runnable implementation class.

//Test code
public class Main {
    public static void main(String[] args) {
        Runnable myThread = new MyThread();
        Thread   thread = new Thread(myThread);
        thread.start();
    }
}

//results of enforcement
resume number:5
resume number:4
resume number:3
resume number:2
resume number:1

The execution order is not fixed. It is uncertain which thread will execute first. It depends on which thread gets the CPU time slice first.

At this point, the basic usage of Thread and Runnable has been introduced.

So many people may say: what's the use of Runnable? It's not as simple as using the Thread subclass, just one object; you have to define two objects to use Runnable! Let's change the code next.

//Test code
public class Main {
    public static void main(String[] args) {
        Runnable myThread = new MyThread();

        Thread   thread1 = new Thread(myThread);
        thread1.start();

        Thread   thread2 = new Thread(myThread);
        thread2.start();

        Thread   thread3 = new Thread(myThread);
        thread3.start();
    }
}

//results of enforcement
resume number:5
resume number:2
resume number:1
resume number:3
resume number:4

Amazing! We created three threads this time, and found that the three threads actually access the same num! This is totally different from the Thread implemented by Thread! It seems that a task is divided into three threads to do it together. It is not repeated at all! Of course, when it comes to sharing data, we have to consider the issue of synchronization and mutual exclusion, but today's topic is not that, so we won't discuss it much.

Let's take a look at the process:

1. Create a myThread object myThread;

2. Three Thread objects are initialized with myThread. In this case, target points to myThread;

3.3 all three Thread objects execute the start() method, that is, three new threads are created, and all execute the myThread.run() method.

The heap is as follows:

Obviously, there is only one num in the heap memory, and all three threads access this num, so 5-1 only prints once.

Runnable interface and Thread can be used together to access shared resources. Of course, synchronization and mutual exclusion mechanism are needed.

Is this shared access possible only through the Runnable interface? In fact, it's not. It's just that Runnable is the best answer. Let's look at two more points of Thread:

//Thread is the implementation class of Runnable
public class Thread implements Runnable {
  ......


    //The parameter of the constructor is the target of Runnable
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
}

Do you understand? Ha ha ha! This method is not good, but it can be done. It is better to use Runnable if necessary.

In addition, with the new feature of Java8, Lambda expression, using Runnable to implement simple threads is more intuitive and concise.

public class Main {
    public static void main(String[] args) {
        new Thread(()->System.out.println("hello world")).start();
    }
}

//Output result
hello world

Process finished with exit code 0

 

If you have any suggestions and questions, please continue to discuss with me. Thank you. QQ: 1446125822.

 

 

 

 

 

 

 

Published 2 original articles, won praise 12, visited 10000+
Private letter follow

Tags: Java Linux Lambda

Posted on Fri, 07 Feb 2020 02:38:53 -0500 by devang23