Java 對多線程編程提供了內置的支持並提供了良好的 API,經過使用 Thread
和 Runnable
兩個基礎類,咱們能夠很方便的建立一個線程:java
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
的類聲明:
segmentfault
咱們能夠知道 Thread
本身也實現了 Runnable
接口,Thread
中 run
方法的實現以下(Thread
啓動以後運行的就是 Thread
中的 run
方法):
多線程
(target 即構造 Thread
時可傳入的 Runnable
對象,不傳入即爲 null
—— 因此繼承 Thread
重寫其 run
方法也是一種建立線程的方式)ide
題外話結束 }this
Runnable.java 的代碼:
spa
Runnable
的 run
方法是不帶返回值的,那若是咱們須要一個耗時任務在執行完以後給予返回值,應該怎麼作呢?線程
第一種方法:在 Runnable
的實現類中設置一個變量 V
,在 run
方法中將其改變爲咱們期待的結果,而後經過一個 getV()
方法將這個變量返回。3d
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; } } }
運行結果:
code
第二種方法:使用 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<V>
是更強大和更通用的類);不然即爲拋出異常的狀況。
使用 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
的選擇。