java線程啓動原理分析

一、前言

不知道哪位古人說:人生三大境界。第一境界是:看山是山看水是水;第二境界是看山不是山看水不是水;第三境界:看山還是山看水還是水。
其實我想對於任何一門技術的學習都是這樣。
形而上下者爲之器,形而上者爲之道。一直很喜歡自己大一的高數老師,老師是老教授了,他講數學,會引申到建築學,計算機科學,以及哲學再到生活中的常識。也能從其他學科、日常生活中,提取出數學的概念。我想,這就是形而上者了。
不勝望之
不多言,這裏我們來深入java底層,看下java表皮之下的筋肉以及內臟。

二、從一段代碼展開

package thread;

/**
 * @author xuyuanpeng
 * @version 1.0
 * @date 2019-05-17 17:04
 */
public class ThreadMain {
    public static void main(String[] args) {
        Thread thread=new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2=new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        log("線程1開始");
        thread.start();
        log("線程1結束");

        log("線程2開始");
        t2.run();
        log("線程2結束");
    }

    public static void log(String msg){
        System.err.print(System.currentTimeMillis());
        System.out.println(">>>"+msg);
    }
    public static void log(){
        log("");
    }
}

這裏可以思考下輸出的結果:
1
2
3
鐺鐺鐺檔

Connected to the target VM, address: ‘127.0.0.1:51304’, transport: ‘socket’
1558085396255>>>線程1開始
1558085396255>>>線程1結束
1558085396255>>>線程2開始
1558085397255>>>線程2結束
Disconnected from the target VM, address: ‘127.0.0.1:51304’, transport: ‘socket’

細心的同學肯定已經發現了
線程1是start的方式啓動,而線程2是run方法啓動
差異在哪?
線程1執行start,並沒有阻塞線程
而線程2的run方法,阻塞了線程。何改咯?┓( ´∀` )┏
爲什麼是這樣的呢?start與run的區別究竟在哪呢?讓我們深入她,張愛玲說,瞭解一個女人最好的通道就是XX,所以讓我們深入她,再瞭解她。

三、JDK源碼分析

1、start方法

public synchronized void start() {
        /**
      /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * 
     * 1、start方法將導致當前線程開始執行。由JVM調用當前線程的run方法。
     * 
     * 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).
     * 
     * 2、結果是 調用start方法的當前線程 和 執行run方法的另一個線程 同時運行。
     * 
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * 3、多次啓動線程永遠不合法。 特別是,線程一旦完成執行就不會重新啓動。
     * 
     * @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.
         * 
         * 4、對於由VM創建/設置的main方法線程或“system”組線程,不會調用此方法。 
         *    未來添加到此方法的任何新功能可能也必須添加到VM中。
         * 
         * A zero status value corresponds to state "NEW".
         * 5、status=0 代表是 status 是 "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. 
         * 
         * 6、通知組該線程即將啓動,以便將其添加到線程組的列表中,
         *    並且減少線程組的未啓動線程數遞減。
         * 
         * */
        group.add(this);

        boolean started = false;
        try {
            //7、調用native方法,底層開啓異步線程,並調用run方法。
            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 
                 * 8、忽略異常。 如果start0拋出一個Throwable,它將被傳遞給調用堆棧。
                 */
            }
        }
    }

start方法用synchronized修飾,爲同步方法;
雖然爲同步方法,但不能避免多次調用問題,用threadStatus來記錄線程狀態,如果線程被多次start會拋出異常;threadStatus的狀態由JVM控制。
使用Runnable時,主線程無法捕獲子線程中的異常狀態。線程的異常,應在線程內部解決。

2、native start0方法

private native void start0();

native 是聲明本地方法,在此處是JVM中的方法。

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

run方法就很簡單了,就是回調了Runable的run()接口
導致Thread寫的@Overwrite void run() 方法直接是在主線程執行,導致阻塞了主線程。

四、總結

到此我們就知道了,start會使重寫的run方法被虛擬機調用,是在子線程中執行的run方法
而直接調用線程的run方法,他是內部回調了run接口,導致直接執行了Runable.run的重寫內容。相當於直接在主線程中執行。

五、參考

https://www.jianshu.com/p/8c16aeea7e1a

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章