線程的創建
我們知道在Java裏線程是通過java.lang.Thread
類來實現的。一般我們創建無返回值的線程會用下面兩個方法:
- 繼承Thread類,重寫run()方法;
- 實現Runnable接口,重寫run()方法;
線程啓動會通過調用start方法來啓動線程而不能直接調用run方法。
這裏就會引出兩個經典的面試題:
- 爲什麼線程啓動是調用start方法來啓動線程而不能直接調用run方法?
- 如果多次調用start方法會發生什麼?
其實答案就是源碼裏,在這之前我們要了解線程的狀態有哪些。
線程的狀態
線程從創建到死亡是會經歷多個狀態的流轉的。它們分別是:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
。
// 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,所以線程只能啓動一次。