Java
對多線程編程提供了內置的支持並提供了良好的 API
,通過使用 Thread
和 Runnable
兩個基礎類,我們可以很方便的創建一個線程:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("線程啟動");
// 耗時操作
System.out.println("線程結束");
}
};
Thread thread = new Thread(runnable); // 創建線程,runnable 作為線程要執行的任務(載體)
thread.start(); // 啟動線程
thread.join(); // 等待線程執行完畢
{ 題外話開始:
通過 Thread
的類聲明:
我們可以知道 Thread
自己也實現了 Runnable
接口,Thread
中 run
方法的實現如下(Thread
啟動之后運行的就是 Thread
中的 run
方法):
(target 即構造Thread
時可傳入的 Runnable
對象,不傳入即為 null
—— 所以繼承 Thread
重寫其 run
方法也是一種創建線程的方式)
題外話結束 }
Runnable.java
的代碼:
Runnable 的 run 方法是不帶返回值的,那如果我們需要一個耗時任務在執行完之后給予返回值,應該怎么做呢?
import java.util.*;
public class RunnableTest {
public static void main(String[] args) throws Exception {
System.out.println("使用 Runnable 獲得返回結果:");
List<Thread> workers = new ArrayList<>(10);
List<AccumRunnable> tasks = new ArrayList<>(10);
// 新建 10 個線程,每個線程分別負責累加 1~10, 11~20, ..., 91~100
for (int i = 0; i < 10; i++) {
AccumRunnable task = new AccumRunnable(i * 10 + 1, (i + 1) * 10);
Thread worker = new Thread(task, "慢速累加器線程" + i);
tasks.add(task);
workers.add(worker);
worker.start();
}
int total = 0;
for (int i = 0, s = workers.size(); i < s; i++) {
workers.get(i).join(); // 等待線程執行完畢
total += tasks.get(i).getResult();
}
System.out.println("\n累加的結果: " + total);
}
static final class AccumRunnable implements Runnable {
private final int begin;
private final int end;
private int result;
public AccumRunnable(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public void run() {
result = 0;
try {
for (int i = begin; i <= end; i++) {
result += i;
Thread.sleep(100);
}
} catch (InterruptedException ex) {
ex.printStackTrace(System.err);
}
System.out.printf("(%s) - 運行結束,結果為 %d\n",
Thread.currentThread().getName(), result);
}
public int getResult() {
return result;
}
}
}
運行結果:
第二種方法:使用 Callable<V>
和 FutureTask<V>
。
Callable<V>
是 JDK1.5
時添加的類,為的就是解決 Runnable
的痛點(沒有返回值和不能拋出異常)。
Callable.java
的代碼:
可以看到參數化類型 V
就是返回的值的類型。
但是查看 Thread
的構造方法,我們發現 Thread
只提供了將 Runnable
作為參數的構造方法,並沒有使用 Callable<V>
的構造方法 —— 所以引出 FutureTask<V>
。
FutureTask<V>
也是 JDK1.5
時添加的類,查看它的類聲明:
可以看到它實現了 RunnableFuture<V>
這個接口,我們再看看 RunnableFuture<V>
:
可以看到 RunnableFuture
接口繼承了 Runnable
接口,那么 RunnableFuture
接口的實現類 FutureTask
必然會去實現 Runnable
接口 —— 所以 FutureTask
可以用來當 Runnable
使用。查看 FutureTask
的構造方法,發現 FutureTask
有兩個構造方法:
第一個構造方法表明我們可以通過一個 Callable
去構造一個 FutureTask
—— 而 FutureTask
實現了 Runnable
接口,從而可以將該任務傳遞給 Thread
去運行;
第二個構造方法是通過一個 Runnable
和一個指定的 result
去構造 FutureTask
。
我們再來看看 FutureTask<V>
通過 RunnableFuture<V>
實現的第二個接口:Future<V>
。
(事實上,RunnableFuture<V>
是在 JDK1.6
時添加的類,我猜測在 JDK1.5
時 FutureTask<V>
應該是直接實現的 Runnable
和 Future<V>
,而不是通過 RunnableFuture<V>)
Future.java
的源碼:
Future<V>
包含的方法有 5 個,本文只關注它兩個 get 相關的方法。通過 Java 的 API 文檔,我們可以知道 get 方法是用來返回和 Future
關聯的任務的結果(即構造 FutureTask<V>
時使用的 Callable<V>
的結果)。帶參數的 get
方法指定一個超時時間,在超時時間內該方法會阻塞當前線程,直到獲得結果之后停止阻塞繼續運行 —— 如果在給定的超時時間內沒有獲得結果,那么便拋出 TimeoutException
異常;不帶參數的 get
可以理解為超時時間無限大,即一直等待直到獲得結果。
(Future 每個方法的詳細用法可以參考 Java 多線程(3))
通過以上我們可以知道,Callable<V>
、Future<V>
、FutureTask<V>
這三個類是相輔相成的。以上提到關鍵類的類關系如下:
現在我們看看 FutureTask
中實現 Runnable
的 run
方法是怎樣的(我們直接看關鍵代碼):
代碼的意思很明確,調用構造 FutureTask<V>
時傳入的 Callable<V>
的 call
方法,如果正常執行完畢,那么通過 set(result)
設置結果,通過 get()
方法得到的即為這個結果 —— 思路和前面第一種方法是一致的(當然 FutureTask
使用 FutureTask<V>
的過程如下:
(1)通過一個 Callable<V>
任務或者一個 Runnable
(一開始就指定 result
)任務構造 FutureTask<V>
;
(2)將 FutureTask<V>
交給 Thread
去運行;
(3)使用 FutureTask<V>
的 get
方法(或者 Thread
的 join
方法)阻塞當前線程直到獲得任務的結果。
現在我們使用 Callable
改寫程序:
import java.util.*;
import java.util.concurrent.*;
public class CallableTest {
public static void main(String[] args) throws Exception {
System.out.println("使用 Callable 獲得返回結果:");
List<FutureTask<Integer>> futureTasks = new ArrayList<>(10);
// 新建 10 個線程,每個線程分別負責累加 1~10, 11~20, ..., 91~100
for (int i = 0; i < 10; i++) {
AccumCallable task = new AccumCallable(i * 10 + 1, (i + 1) * 10);
FutureTask<Integer> futureTask = new FutureTask<>(task);
futureTasks.add(futureTask);
Thread worker = new Thread(futureTask, "慢速累加器線程" + i);
worker.start();
}
int total = 0;
for (FutureTask<Integer> futureTask : futureTasks) {
total += futureTask.get(); // get() 方法會阻塞直到獲得結果
}
System.out.println("累加的結果: " + total);
}
static final class AccumCallable implements Callable<Integer> {
private final int begin;
private final int end;
public AccumCallable(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public Integer call() throws Exception {
int result = 0;
for (int i = begin; i <= end; i++) {
result += i;
Thread.sleep(100);
}
System.out.printf("(%s) - 運行結束,結果為 %d\n",
Thread.currentThread().getName(), result);
return result;
}
}
}
運行結果:
可以看到使用 Callable<V>
+ FutureTask<V>
的程序代碼要比 Runnable
的代碼更簡潔和方便 —— 當需要線程執行完成返回結果時(或者任務需要拋出異常),Callable<V>
是優先於 Runnable
的選擇。
原文地址:https://segmentfault.com/a/1190000007767231
推薦
文末
歡迎關注個人微信公眾號:Coder編程
歡迎關注Coder編程公眾號,主要分享數據結構與算法、Java相關知識體系、框架知識及原理、Spring全家桶、微服務項目實戰、DevOps實踐之路、每日一篇互聯網大廠面試或筆試題以及PMP項目管理知識等。更多精彩內容正在路上~
文章收錄至
Github: https://github.com/CoderMerlin/coder-programming
Gitee: https://gitee.com/573059382/coder-programming
歡迎關注並star~