java線程獲取結果Callable、Future、FutureTask
一、提個問題
當我們創建一個線程時,我們想獲取線程運行完成後的結果,是否可以用回調的方法來實現?
答案是肯定的,可以。例如:
//定義一個回調接口
interface Callable {
void call(int num);
}
public class FutureTest {
public static void main(String[] args) {
//創建回調對象
Callable callable = new Callable() {
@Override
public void call(int num) {
System.out.println("線程運行結果值 num=="+num);
}
};
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程"+Thread.currentThread().getName()+" 開始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//調用回調方法 把結果傳出去
callable.call(100);
System.out.println("線程"+Thread.currentThread().getName()+" 結束");
}
}, "t1").start();
System.out.println("主線程結束");
}
}
運行結果:
主線程結束
線程t1 開始
線程運行結果值 num==100
線程t1 結束
這種方式的實現有三個缺點:
1、必須要創建回調接口。而且線程運行中可能產生異常,那麼回調接口至少包含成功回調和錯誤回調兩個方法。
2、當線程運行後,只能等到線程回調接口,本身我們沒有辦法進行取消操作。
3、如果要重複獲取同樣線程運行結果的值,還是隻能重新運行線程。當然你也可以使用一個變量緩存結果值。
那麼有沒有一種優化的方式呢?java中提供了Future與Callable的模式。
二、理解 Runnable、Callable與Future
先看一下定義
Runnable
Runnable它只有一個run()函數,用於將耗時操作寫在其中,該函數沒有返回值。然後使用某個線程去執行該Runnable即可實現多線程,Thread類在調用start()函數後就是執行的是Runnable的run()函數.
public interface Runnable {
/*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Callable
Callable中有一個call()函數,但是call()函數有返回值,而Runnable的run()函數不能將結果返回給客戶程序。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable 與Runnable的區別
1、Callable定義的方法是call,而Runnable定義的方法是run。
2、Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
3、 Callable的call方法可拋出異常,而Runnable的run方法不能拋出異常
Future
Executor就是Runnable和Callable的調度容器,Future就是對於具體的Runnable或者Callable任務的執行結果進行
取消、查詢是否完成、獲取結果、設置結果操作進行的再次封裝。注意其get方法會阻塞,直到任務返回結果。
public interface Future<V> {
/**
* Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, has already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* Returns <tt>true</tt> if this task was cancelled before it completed
* normally.
*/
boolean isCancelled();
/**
* Returns <tt>true</tt> if this task completed.
*
*/
boolean isDone();
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
*/
V get() throws InterruptedException, ExecutionException;
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result, if available.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Future<V>接口是用來獲取異步計算結果的,說白了就是對具體的Runnable或者Callable對象任務執行的結果進行獲取(get()),取消(cancel()),判斷是否完成等操作
在Future接口中聲明瞭5個方法,下面依次解釋每個方法的作用:
1、cancel方法 用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。
參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。
如果任務已經完成,則無論mayInterruptIfRunning爲true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;
如果任務正在執行,若mayInterruptIfRunning設置爲true,則返回true,若mayInterruptIfRunning設置爲false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning爲true還是false,肯定返回true。
2、isCancelled方法 表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。
3、isDone方法 表示任務是否已經完成,若任務完成,則返回true;
4、get()方法 用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;
5、get(long timeout, TimeUnit unit) 用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。
也就是說Future提供了三種功能:
1)判斷任務是否完成;
2)能夠中斷任務;
3)能夠獲取任務執行結果。
因爲Future只是一個接口,所以是無法直接用來創建對象使用的,因此就有了下面的FutureTask
FutureTask
看一下FutureTask的實現類
public class FutureTask<V> implements RunnableFuture<V>
說明FutureTask類實現了RunnableFuture接口,我們看一下RunnableFuture接口的實現
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
可以看出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實現了RunnableFuture接口。所以FutureTask既可以作爲Runnable被線程執行,又可以作爲Future得到Callable的返回值。
另外FutureTaslk還可以包裝Runnable和Callable<V>, 由構造函數注入依賴
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
上面代碼塊可以看出:
Runnable注入會被Executors.callable()函數轉換爲Callable類型,即FutureTask最終都是執行Callable類型的任務
因此FutureTask是Future也是Runnable,又是包裝了的Callable( 如果是Runnable最終也會被轉換爲Callable )。
Callable 和 Future接口的區別
1、Callable規定的方法是call(),而Runnable規定的方法是run().
2、Callable的任務執行後可返回值,而Runnable的任務是不能返回值的。
3、call()方法可拋出異常,而run()方法是不能拋出異常的。
4、運行Callable任務可拿到一個Future對象, Future表示異步計算的結果。
5、它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。
6、 通過Future對象可瞭解任務執行情況,可取消任務的執行,還可獲取任務執行的結果。
7、Callable是類似於Runnable的接口,實現Callable接口的類和實現Runnable的類都是可被其它線程執行的任務。
三、解決問題
搞清楚了概念我們就來解決文章開頭提出的問題
1、使用Callable+Future獲取執行結果
package com.demo.test;
import java.util.concurrent.Callable;
//定義一個任務
public class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子線程在進行計算");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++)
sum += i;
return sum;
}
}
package com.demo.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableTest {
public static void main(String[] args) {
//創建線程池
ExecutorService executor = Executors.newCachedThreadPool();
//創建Callable對象任務
Task task = new Task();
//提交任務並獲取執行結果
Future<Integer> result = executor.submit(task);
//關閉線程池
executor.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主線程在執行任務");
try {
if(result.get()!=null){
System.out.println("task運行結果"+result.get());
}else{
System.out.println("未獲取到結果");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任務執行完畢");
}
}
2、使用Callable+FutureTask獲取執行結果
package com.demo.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class CallableTest1 {
public static void main(String[] args) {
//第一種方式
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);
executor.shutdown();
//第二種方式,注意這種方式和第一種方式效果是類似的,只不過一個使用的是ExecutorService,一個使用的是Thread
// Task task = new Task();
// FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
// Thread thread = new Thread(futureTask);
// thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主線程在執行任務");
try {
if(futureTask.get()!=null){
System.out.println("task運行結果"+futureTask.get());
}else{
System.out.println("future.get()未獲取到結果");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任務執行完畢");
}
}