面試題:
- Java中建立線程有幾種方式。
- 不同的建立方式有什麼差別。
- 如何啟動一個線程。
Java中建立線程的方式
- JDK源碼中的描述:兩種
- 第一種是繼承Thread類,重寫其run()方法()。
- 第二種是實作Runnable接口,重寫run()方法,再将Runnable執行個體傳給Thread,Thread類最終會調用
(target即為Runnable執行個體)方法來執行。target.run()
- 代碼示範
/**
* 通過繼承Thread類,重寫run()方法實作的線程
*
* @author futao
* @date 2020/6/4
*/
public class ByThread extends Thread {
@Override
public void run() {
System.out.println("通過繼承Thread實作的多線程:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
new ByThread().start();
}
}
/**
* 通過實作Runnable接口,重寫run()方法實作的線程
*
* @author futao
* @date 2020/6/4
*/
public class ByRunnable implements Runnable {
@Override
public void run() {
System.out.println("通過實作接口Runnable實作的多線程:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
new Thread(new ByRunnable()).start();
}
}
核心思想
從源代碼的角度來看,建立線程的方式隻有一種,唯一的途徑就是執行個體化一個Thread對象,通過
thread.start()
來啟動線程。而繼承Thread類和實作Runnable接口,隻不過是對方法執行單元(即
run()
方法)的兩種不同實作。
兩種方式的對比:
- 從代碼架構角度:使用Runnable接口得我方式可以将線程的建立/停止/狀态管理等與真正的業務邏輯解耦,使Runnable子類隻需要關注真正的業務即可。
- 從性能損耗角度:使用繼承Thread的方法,每次執行任務都需要啟動一個新的線程,建立一個新的Thread執行個體,任務執行完畢之後還需要進行銷毀。對性能的損耗比較嚴重。而實作Runnable接口的方式,可以實作對線程的複用,每次給線程傳遞不同的任務(Runnable執行個體)即可,不需要頻繁的建立與銷毀線程。( 參考線程池的執行過程 )
- 從可擴充性角度:Java隻支援單繼承,是以實作Runnable接口的方式更好,避免繼承的局限,友善後續對程式進行擴充。
Q: 如果同時繼承Thread類和實作Runnable接口,會發生什麼?
/**
* @author futao
* @date 2020/6/4
*/
public class Both {
public static void main(String[] args) {
new Thread(() -> {
//通過lambda表達式建立Runnable子類對象
System.out.println("來自實作Runnable接口的run()方法");
}) {
//Thread的匿名内部類,直接重寫Thread父類的run()方法
@Override
public void run() {
System.out.println("來自重寫Thread類的run()方法");
}
}.start();
}
}
- 從結果可以看到,最終線程執行的是匿名内部類的run()方法。原因是在我們将Thread的start()方法重寫之後不會再執行調用Runnable.run()方法。而執行我們重寫之後的run()方法。
@Override
public void run() {
if (target != null) {
target.run();
}
}
錯誤觀點
- 線程池: 通過線程工廠
建立線程,而ThreadFactory.newThread()
方法中也是通過執行個體化Thread對象的方式建立線程。ThreadFactory.newThread()
ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
});
- 通過
與FutureTask<>
: FutureTask實際上實作了Runnable接口,并且在重寫的run()方法中調用了傳遞進來的Callable對象的call()方法。是以這種方式隻不過是對Runnable方式的一種封裝而已。本質上也隻是實作線程執行單元的一種方法,最終需要将FutureTask對象傳入Thread()對象進行執行。Callable
/**
* @author futao
* @date 2020/6/6.
*/
public class CallableFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("666");
Thread.sleep(3000L);
return Thread.currentThread().getName();
}
});
new Thread(task).start();
//将會阻塞,直到線程任務執行完畢
System.out.println(task.get());
}
}
線程的啟動
- 調用Thread對象的
方法。三個過程:start()
- 判斷線程狀态。
- 加入線程組。
- 執行native方法
。start0()
- 直接調用
方法:隻是普通方法調用,不會開啟新的線程。run()
-
方法隻能被調用一次,如果第二次調用,将抛出異常,即啟動過程的第一步:檢查線程狀态不通過。start()