在 Java 中,有多種方式來實現多線程。下面通過實例來分別介紹繼承 Thread 類、實現 Runnable 接口、Callable接口來創建線程。
1、繼承 Thread 類
1.1類圖
先來看下Thread的類圖:
從類圖中可以看出,Runnable
接口是一個函數式接口,裏面只有一個抽象的run()
方法,Thread 類本質上就是實現了 Runnable 接口的一個實例。
啓動線程的唯一方法就是通過 調用Thread類的 start()方法。start()方法是一個 native
方法,它會啓動一個新線程,並執行 run()方法。
這種方式實現多線程很簡單,通過自己的類直接 extend Thread
,並複寫 run()方法,就可以啓動新線程並執行自己定義的 run()方法。
對於線程的啓動過程,會在下一章節進行詳細講解。
1.2 實例
public class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("運行線程-->"+Thread.currentThread().getName());
}
public static void main(String[] args) {
ThreadDemo threadDemo1 = new ThreadDemo();
ThreadDemo threadDemo2 = new ThreadDemo();
threadDemo1.start();
threadDemo2.start();
}
}
2、實現 Runnable 接口
如果自己的類已經 extends 另一個類,就無法直接 繼承Thread,此時,可以實現一個 Runnable 接口。定義runnable接口的實現類,並重寫該接口的run()方法。
實現方式如下:
public class RunnableDemo implements Runnable{
@Override
public void run() {
System.out.println("運行線程-->"+Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t1=new Thread(new RunnableDemo());
t1.start();
System.out.println("運行線程-->"+Thread.currentThread().getName());
}
}
3、實現 Callable 接口
3.1 類圖
加入了Thread繼承關係的類圖:
通過類圖可以清楚的看出,FutureTask
類實現了RunnableFuture
接口,RunnableFuture
又繼承了Runnable
和Future
兩個接口。
Callable對象不能直接作爲Thread對象的target,因爲Callable接口是 Java 5 新增的接口,不是Runnable接口的子接口。對於這個問題的解決方案,就引入 Future接口,此接口可以接受call() 的返回值,RunnableFuture接口是Future接口和Runnable接口的子接口,可以作爲Thread對象的target 。並且, Future 接口提供了一個實現類:FutureTask 。
FutureTask實現了RunnableFuture接口,可以作爲 Thread對象的target。
實現 Callable
接口可以通過 Future Task 包裝器來創建 Thread 線程,有的時候,我們可能需要讓一步執行的線程在執行完成以後,提供一個返回值給到當前的主線程,主線程需要依賴這個值進行後續的邏輯處理,那麼這個時候,就需要用到帶返回值的線程了。
簡單理解一下就是:這是一個可以帶返回值的線程。
3.2實例
public class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("運行線程-->"+Thread.currentThread().getName());
return "SUCCESS";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo2 callableDemo = new CallableDemo2();
FutureTask<String> futureTask = new FutureTask<>(callableDemo2);
new Thread(futureTask).start();
Thread.sleep(100);
System.out.println(futureTask.get());
}
}
4、Runnable和Callable區別
通過上述三種方式,其實可以歸爲兩類:繼承類和實現接口兩種方式。相比繼承, 接口實現可以更加靈活,不會受限於Java的單繼承機制。並且通過實現接口的方式可以共享資源,適合多線程處理同一資源的情況。下面簡單介紹一下兩種接口類實現的區別:
-
1)Callable規定的方法是call(),Runnable規定的方法是run();
-
2)Callable的任務執行後可返回值,而Runnable的任務是不能返回值得;
-
3)call方法可以拋出異常,run方法不可以,因爲run方法本身沒有拋出異常,所以自定義的線程類在重寫run的時候也無法拋出異常;
-
4)運行Callable任務可以拿到一個Future對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。通過Future對象可以瞭解任務執行情況,可取消任務的執行,還可獲取執行結果。
5、start()和run()的區別
- start()方法用來開啓線程,但是線程開啓後並沒有立即執行,他需要獲取cpu的執行權(分配的時間片)纔可以執行;
- run()方法是由jvm創建完本地操作系統級線程後回調的方法,不可以手動調用(否則就是普通方法)。
總結:
本篇主要介紹了線程的三種創建方式,下一篇來介紹一下線程的啓動過程,一個線程到底是怎麼創建出來,又是怎麼啓動運行的呢,在下一篇你將會找到答案。