Callable接口
有兩種創建線程的方法-一種是通過創建Thread類,另一種是通過使用Runnable創建線程。但是,Runnable缺少的一項功能是,當線程終止時(即run()完成時),我們無法使線程返回結果。為了支持此功能,Java中提供了Callable接口。
- 為了實現Runnable,需要實現不返回任何內容的run()方法,而對於Callable,需要實現在完成時返回結果的call()方法。請注意,不能使用Callable創建線程,只能使用Runnable創建線程。
- 另一個區別是call()方法可以引發異常,而run()則不能。
-
為實現Callable而必須重寫call方法。
// Java program to illustrate Callable // to return a random number import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; class CallableExample implements Callable { public Object call() throws Exception { // Create random number generator Random generator = new Random(); Integer randomNumber = generator.nextInt(5); // To simulate a heavy computation, // we delay the thread for some random time Thread.sleep(randomNumber * 1000); return randomNumber; } }
Futrue接口
當call()方法完成時,結果必須存儲在主線程已知的對象中,以便主線程可以知道該線程返回的結果。為此,可以使用Future對象。將Future視為保存結果的對象–它可能暫時不保存結果,但將來會保存(一旦Callable返回)。因此,Future基本上是主線程可以跟蹤進度以及其他線程的結果的一種方式。要實現此接口,必須重寫5種方法,但是由於下面的示例使用了庫中的具體實現,因此這里僅列出了重要的方法。
- public boolean cancel(boolean mayInterrupt):用於停止任務。如果尚未啟動,它將停止任務。如果已啟動,則僅在mayInterrupt為true時才會中斷任務。
- public Object get()拋出InterruptedException,ExecutionException:用於獲取任務的結果。如果任務完成,它將立即返回結果,否則將等待任務完成,然后返回結果。
- public boolean isDone():如果任務完成,則返回true,否則返回false
可以看到Callable和Future做兩件事-Callable與Runnable類似,因為它封裝了要在另一個線程上運行的任務,而Future用於存儲從另一個線程獲得的結果。實際上,future也可以與Runnable一起使用。
要創建線程,需要Runnable。為了獲得結果,需要future。
Java庫具有具體的FutureTask類型,該類型實現Runnable和Future,並方便地將兩種功能組合在一起。
可以通過為其構造函數提供Callable來創建FutureTask。然后,將FutureTask對象提供給Thread的構造函數以創建Thread對象。因此,間接地使用Callable創建線程。
1.使用Callable和Future的完整示例
package com.example.thread.callable; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.*; /** * @author: GuanBin * @date: Created in 下午11:19 2019/10/31 */ public class TestCallable implements Callable<Object> { private int taskNum; public TestCallable(int taskNum) { this.taskNum = taskNum; }
//1,2主要區別是創建線程的方式 public static void main(String[] args) throws ExecutionException, InterruptedException { test1(); test2(); } /** * 使用Executors.newFixedThreadPool創建線程池 * @throws InterruptedException * @throws ExecutionException */ private static void test1() throws InterruptedException, ExecutionException { System.out.println("----程序開始運行----"); Date date1 = new Date(); int taskSize=5; ExecutorService pool = Executors.newFixedThreadPool(taskSize); List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new TestCallable(i); // 執行任務並獲取Future對象 Future f = pool.submit(c); list.add(f); } // 關閉線程池 pool.shutdown(); // 獲取所有並發任務的運行結果 for (Future f : list) { // 從Future對象上獲取任務的返回值,並輸出到控制台 System.out.println(">>>" + f.get().toString()); //OPTION + return 拋異常 } Date date2 = new Date(); System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } /** * 線程直接使用new Thread來創建 * @throws ExecutionException * @throws InterruptedException */ private static void test2() throws ExecutionException, InterruptedException { System.out.println("----程序開始運行----"); Date date1 = new Date(); int taskSize=5; FutureTask[] randomNumberTasks = new FutureTask[5]; List<Future> list = new ArrayList<Future>(); for (int i = 0; i < randomNumberTasks.length; i++) { Callable c = new TestCallable(i); // 執行任務並獲取Future對象 randomNumberTasks[i]= new FutureTask(c); Thread t = new Thread(randomNumberTasks[i]); t.start(); } // 獲取所有並發任務的運行結果 for (Future f : randomNumberTasks) { // 從Future對象上獲取任務的返回值,並輸 System.out.println(">>>" + f.get().toString()); //OPTION + return 拋異常 } Date date2 = new Date(); System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } /** * call方法的實現,主要用於執行線程的具體實現,並返回結果 * @return * @throws Exception */ @Override public Object call() throws Exception { System.out.println(">>>" + taskNum + "任務啟動"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任務終止"); return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】"; } }
輸出
----程序開始運行---- >>>0任務啟動 >>>1任務啟動 >>>2任務啟動 >>>3任務啟動 >>>4任務啟動 >>>0任務終止 >>>0任務返回運行結果,當前任務時間【1002毫秒】 >>>1任務終止 >>>2任務終止 >>>4任務終止 >>>1任務返回運行結果,當前任務時間【1005毫秒】 >>>2任務返回運行結果,當前任務時間【1005毫秒】 >>>3任務終止 >>>3任務返回運行結果,當前任務時間【1005毫秒】 >>>4任務返回運行結果,當前任務時間【1005毫秒】 ----程序結束運行----,程序運行時間【1007毫秒】 Process finished with exit code 0
2.使用Callable和FutureTask的完整示例
// Java program to illustrate Callable and FutureTask // for random number generation import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; class CallableExample implements Callable { public Object call() throws Exception { Random generator = new Random(); Integer randomNumber = generator.nextInt(5); Thread.sleep(randomNumber * 1000); return randomNumber; } } public class CallableFutureTest { public static void main(String[] args) throws Exception { // FutureTask is a concrete class that // implements both Runnable and Future FutureTask[] randomNumberTasks = new FutureTask[5]; for (int i = 0; i < 5; i++) { Callable callable = new CallableExample(); // Create the FutureTask with Callable randomNumberTasks[i] = new FutureTask(callable); // As it implements Runnable, create Thread // with FutureTask Thread t = new Thread(randomNumberTasks[i]); t.start(); } for (int i = 0; i < 5; i++) { // As it implements Future, we can call get() System.out.println(randomNumberTasks[i].get()); // This method blocks till the result is obtained // The get method can throw checked exceptions // like when it is interrupted. This is the reason // for adding the throws clause to main } } }
啟動線程后,與線程的所有交互都使用FutureTask,因為它實現了Future接口。因此,不需要存儲Thread對象。使用FutureTask對象,還可以取消任務,檢查任務是否完成或嘗試獲取結果。
3.使用Runnable來獲取返回結果的實現
// Java program to illustrate Runnable // for random number generation import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; class RunnableExample implements Runnable { // Shared object to store result private Object result = null; public void run() { Random generator = new Random(); Integer randomNumber = generator.nextInt(5); // As run cannot throw any Exception try { Thread.sleep(randomNumber * 1000); } catch (InterruptedException e) { e.printStackTrace(); } // Store the return value in result when done result = randomNumber; // Wake up threads blocked on the get() method synchronized(this) { notifyAll(); } } public synchronized Object get() throws InterruptedException { while (result == null) wait(); return result; } } // Code is almost same as the previous example with a // few changes made to use Runnable instead of Callable public class RunnableTest { public static void main(String[] args) throws Exception { RunnableExample[] randomNumberTasks = new RunnableExample[5]; for (int i = 0; i < 5; i++) { randomNumberTasks[i] = new RunnableExample(); Thread t = new Thread(randomNumberTasks[i]); t.start(); } for (int i = 0; i < 5; i++) System.out.println(randomNumberTasks[i].get()); } }