Thread啓動線程的start方法能執行多次嗎?

線程的創建

我們知道在Java裏線程是通過java.lang.Thread類來實現的。一般我們創建無返回值的線程會用下面兩個方法:

  1. 繼承Thread類,重寫run()方法;
  2. 實現Runnable接口,重寫run()方法;

線程啓動會通過調用start方法來啓動線程而不能直接調用run方法。

這裏就會引出兩個經典的面試題:

  1. 爲什麼線程啓動是調用start方法來啓動線程而不能直接調用run方法?
  2. 如果多次調用start方法會發生什麼?

其實答案就是源碼裏,在這之前我們要了解線程的狀態有哪些。

線程的狀態

線程從創建到死亡是會經歷多個狀態的流轉的。它們分別是:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

Java-Thread-Status

// Thread類的內部的枚舉類定義了線程的狀態
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

start() 方法 & run()方法

爲什麼線程啓動是調用start方法來啓動線程而不能直接調用run方法?

從上圖中,我們就可以看到,在New了一個線程後,首先進入初始態,然後調用start()方法來到就緒態,這裏並不會立即執行線程裏的任務,而是會等待系統資源分配,當分配到時間片後就可以開始運行了。 start()方法是一個native方法,它將啓動一個新線程,並執行run()方法,這是真正的多線程工作。 而直接執行 run() 方法,會把 run 方法當成一個 main 線程下的普通方法去執行,並不會在某個線程中執行它,所以這並不是多線程工作。這就是爲什麼調用 start() 方法時會執行 run() 方法,爲什麼不能直接調用 run() 方法的原因了。

Example:

public class ThreadDemo    
		public static void main(String[] args) {
        Thread t1 = new Thread(new Task1());
        Thread t2 = new Thread(new Task2());

  			// 測試1
        t1.start();
        t2.start();

 				// 測試2
        t1.run();
        t2.run();

    }
}

class Task1 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Task1: " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Task2 implements Runnable {

    @Override
    public void run() {
        for (int i = 10; i > 0; i--) {
            System.out.println("Task2: " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


// 測試1輸出
Task1: 0
Task2: 10
Task1: 1
Task2: 9
Task1: 2
Task2: 8
Task1: 3
Task2: 7
Task1: 4
Task2: 6
Task1: 5
Task2: 5
Task1: 6
Task2: 4
Task1: 7
Task2: 3
Task1: 8
Task2: 2
Task1: 9
Task2: 1
我們可以看到Task1 和 Task2是交替打印的,是多線程在運行。
// 測試2輸出
Task1: 0
Task1: 1
Task1: 2
Task1: 3
Task1: 4
Task1: 5
Task1: 6
Task1: 7
Task1: 8
Task1: 9
Task2: 10
Task2: 9
Task2: 8
Task2: 7
Task2: 6
Task2: 5
Task2: 4
Task2: 3
Task2: 2
Task2: 1
這個的輸出是串行的,Task1 執行完才執行 Task2,所以不是多線程,是普通方法。

如果多次調用start方法會發生什麼?

首先我們先測試一下。

Example:

public class ThreadDemo    
		public static void main(String[] args) {
        Thread t1 = new Thread(new Task1());
        Thread t2 = new Thread(new Task2());

  			// 測試3
        t1.start();
        t1.start();
    }
}

// 測試3輸出
Task1: 0
Task...
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Thread.java:708)
Task...

只有第一次成功執行,後面就拋出了異常java.lang.IllegalThreadStateException,讓我們來從下面的源碼裏看看吧,在start方法進來後就會判斷線程的狀態,如果不是初始態狀態就會拋出異常,所以第二次執行就會報錯,因爲線程的狀態已經發生改變。

源碼

start()方法源碼:

    public synchronized void start() {  
        // 如果線程不是"NEW狀態",則拋出異常!  
        if (threadStatus != 0)  
            throw new IllegalThreadStateException();  
        // 將線程添加到ThreadGroup中  
        group.add(this);  
        boolean started = false;  
        try {  
            // 通過start0()啓動線程,新線程會調用run()方法  
            start0();  
            // 設置started標記=true  
            started = true;  
        } finally {  
            try {  
                if (!started) {  
                    group.threadStartFailed(this);  
                }  
            } catch (Throwable ignore) {  
            }  
        }  
    }  

run方法源碼:

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

總結

start()方法是用來啓動線程,真正實現了多線程運行。

run()方法是一個普通方法。

調用start()方法後會先判斷線程的狀態是否爲NEW,所以線程只能啓動一次。


求關注、分享、在看!!! 你的支持是我創作最大的動力。

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