Java基礎——跟蹤啓動Thread的兩種方式

先看Java開啓Thread的兩種寫法

1. 繼承Thread類,重寫run方法。

     class MyThread extends Thread{

        @Override
        public void run() {

        }
    }
    void startThread(){
        MyThread myThread = new MyThread();
        myThread.start();
    }

2. 實現Runable接口的run方法,並傳入Thread的構造方法。

    class MyTask implements Runnable{

        @Override
        public void run() {

        }
    }
    void startThread(){
        MyTask myThread = new MyTask();
        Thread thread = new Thread(myThread);
        thread.start();
    }

很多人會有疑問,這兩種方式開啓線程在底層實現上有差別嗎?

帶着這個問題我們再來看JDK源碼中Thread這個類。

public class Thread implements Runnable {
    //省略2000多行代碼...
}

Thread類本身實現了Runable接口。

繼續查看JDK源碼中的Runnable接口是怎麼寫的。

@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();
}

顯而易見,Runnable接口只有一個方法run()。然後我們查看Thread類是怎麼實現run方法的。

    /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

而這個target又是什麼?這是Thread類的一個Runnable類型的成員變量,通過搜索發現只有在init()和exit()方法中對這個成員變量進行賦值。

這裏先看init方法

    /**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();

        if (g == null) {

            if (security != null) {
                g = security.getThreadGroup();
            }


        }

        g.checkAccess();

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();

        this.target = target;

        init2(parent);

        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

這個方法基本工作就是初始化各個成員變量,通過查找發現Thread的一系列構造方法最終都調用了這個方法,而以上第二種方法在Thread構造方法中傳入的Runnable對象的引用最終就是賦值給了這個成員變量target,會在Thread的run方法中調用Runnable對象的run方法。

    /**
     * Allocates a new {@code Thread} object. This constructor has the same
     * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
     * {@code (null, target, gname)}, where {@code gname} is a newly generated
     * name. Automatically generated names are of the form
     * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
     *
     * @param  target
     *         the object whose {@code run} method is invoked when this thread
     *         is started. If {@code null}, this classes {@code run} method does
     *         nothing.
     */
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

通過分析init代碼發現,在構建出一個Thread對象時,其實並沒有真正在虛擬機內存空間中給這個線程分配內存。虛擬機真正創建線程是在調用start方法時,通過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()
     */
    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".
         */
        // Android-changed: Replace unused threadStatus field with started field.
        // The threadStatus field is unused on Android.
        if (started)
            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);

        // Android-changed: Use field instead of local variable.
        // It is necessary to remember the state of this across calls to this method so that it
        // can throw an IllegalThreadStateException if this method is called on an already
        // started thread.
        started = false;
        try {
            // Android-changed: Use Android specific nativeCreate() method to create/start thread.
            // start0();
            nativeCreate(this, stackSize, daemon);
            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 */
            }
        }
    }

    // Android-changed: Use Android specific nativeCreate() method to create/start thread.
    // The upstream native method start0() only takes a reference to this object and so must obtain
    // the stack size and daemon status directly from the field whereas Android supplies the values
    // explicitly on the method call.
    // private native void start0();
    private native static void nativeCreate(Thread t, long stackSize, boolean daemon);

這裏關注nativeCreate這個方法,這是一個本地方法,具體實現不深入研究,最終通過start中的這個方法開啓一個線程,而真正在線程中執行的代碼就是run方法中根據具體業務實現的代碼。

回答上面的問題:兩種創建線程的方法,區別就是對run方法的處理,第一種方式通過直接繼承重寫來實現,第二種方式則通過傳參組合的方式實現,對線程的最終執行並無本質區別。

任何線程都是Runnable類型,Runnable這個詞用的相當到位,可以理解爲可運行狀態,但不一定是正在運行的,因爲線程是CPU的最小調度單元,CPU的快速調度線程狀態的切換對於用戶幾乎是無感知的。

關於操作系統線程的概念,可以閱讀《操作系統精髓與設計原理》知識梳理 之線程篇

 

發佈了14 篇原創文章 · 獲贊 3 · 訪問量 1697
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章