Thread類的start()和run()的區別

先給結論:
  1. start()屬於Thread本身,run()方法實現於Runnable接口;
  2. 調用start()方法時會調用native的start0()方法,啓用一個線程,在獲取到CPU時間片時調用run()方法;直接調用run()方法,僅僅是普通方法調用,並不會啓用線程;
  3. start()方法是啓用一個線程,所以不會阻塞調用它的線程;run()方法不會啓用線程,所以會阻塞調用它的線程,直至run()方法體執行結束;
  4. run()方法一般可以被多次調用,start()是synchronized修飾的,在調用start0()方法前會確認當前線程狀態,如果已經被啓用,將會拋出異常。

一、start()和run()方法的源碼

start()方法源碼:

public synchronized void start() {
        // 判斷線程狀態,0爲NEW
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
		// 將線程添加到ThreadGroup組中
        group.add(this);

        boolean started = false;
        try {
            // 調用native方法start0(),當cpu空閒時將會執行此線程
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
              
            }
        }
    }

run()方法源碼:

// 實現runnable接口的run()
@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

二、驗證是否創建線程與執行順序

上代碼:

public class ThreadDemo extends Thread{
    // 線程體,run()方法運行完,這個線程隨即終止
    public void run() {
        // 打印當前正在執行的線程名稱
        System.out.println("當前線程名稱:" + Thread.currentThread().getName());// 2
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        // 回調Runnable接口的run()方法
        threadDemo.run();// 1
        System.out.println("main 當前線程名稱:" + Thread.currentThread().getName());// 3
        
        ThreadDemo threadDemo1 = new ThreadDemo();
        // 調用Thread的start()方法
        threadDemo1.start();// 4
        System.out.println("this is test");// 5

    }
}

執行結果:

當前線程名稱:main
main 當前線程名稱:main
this is test
當前線程名稱:Thread-1

顯然,當1處的run()方法被調用時,執行了2處的打印語句,而3處的打印語句則是等待1調用的run()執行結束才執行;而4處的start()方法也調用了2處的打印語句,卻在5之後輸出了,並且線程名稱不是main。

分析:當main線程調用run()方法時,與調用普通方法一樣,等run()方法體執行結束才繼續執行下面的語句(阻塞了之後的語句的執行);main線程調用start()方法時,啓用了另一個線程Thread-1,但是由於當前main線程佔用着CPU,所以Thread-1沒有立即執行,而是等5處的打印語句執行完(此時main方法執行完畢,讓出了CPU)才執行(未阻塞main線程)。【實際上這裏的”佔用“、”讓出“並不正確!!!見第三節】

三、利用sleep()方法"阻塞"main線程

首先要爲CPU正名:並不是誰想讓老子執行,老子就執行的

修改代碼:

public class ThreadDemo extends Thread{

    // 線程體,run()方法運行完,這個線程隨即終止
    public void run() {
        // 來一個耗時的循環
        int i = 0;
        while (i < Integer.MAX_VALUE) {
            i++;
        }
        System.out.println("當前線程名稱:" + Thread.currentThread().getName());
    }


    public static void main(String[] args) throws InterruptedException {
        ThreadDemo threadDemo1 = new ThreadDemo();
        // 調用Thread的start()方法
        threadDemo1.start();
        Thread.sleep(1);// 6
        // Thread.sleep(1000); // 7
        System.out.println("this is test");// 8

    }
}

註釋7處的代碼,執行結果爲:

this is test
當前線程名稱:Thread-0

Thread-0的輸出在打印語句8的後面。這意味着CPU在執行main線程時並沒有等start()方法執行完就執行了8,這似乎與第二節中的表述不符啊???不急,繼續看。

當我們註釋6處的代碼,放開7處的代碼時,執行結果爲:

當前線程名稱:Thread-0
this is test

可以發現:main線程多睡了999ms,結果被Thread-0插隊了,爲什麼會這樣?其實是CPU在搞鬼,首先現在的CPU並非單核,不是單核就可以利用其他的核來”乾點別的“;其次,假設上述代碼6處執行前,Thread-0因爲沒有CPU執行而處於等待狀態,那麼在6中的sleep語句執行完那一刻將會有兩個線程(main和Thread-0)等着要被執行,那麼這個時候CPU該去執行誰呢:

CPU調度規則,順序隨機

說白了,這個時候得先執行main還是先執行Thread-0得看CPU臉色;這裏涉及到CPU的調度規則等相關內容,不是很熟悉,暫不展開,有興趣可以自行搜索。

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