Callable
在Java
中,創建線程一般有兩種方式,一種是繼承Thread
類,一種是實現Runnable
接口。然而,這兩種方式的缺點是在線程任務執行結束后,無法獲取執行結果。我們一般只能采用共享變量或共享存儲區以及線程通信的方式實現獲得任務結果的目的。
不過,Java
中,也提供了使用Callable
和Future
來實現獲取任務結果的操作。Callable
用來執行任務,產生結果,而Future
用來獲得結果。
Callable
接口與Runnable
接口是否相似,查看源碼,可知Callable
接口的定義如下:
@FunctionalInterface
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;
}
可以看到,與Runnable
接口不同之處在於,call
方法帶有泛型返回值V。
Future
常用方法
V get()
:獲取異步執行的結果,如果沒有結果可用,此方法會阻塞直到異步計算完成。V get(Long timeout , TimeUnit unit)
:獲取異步執行結果,如果沒有結果可用,此方法會阻塞,但是會有時間限制,如果阻塞時間超過設定的timeout時間,該方法將拋出異常。boolean isDone()
:如果任務執行結束,無論是正常結束或是中途取消還是發生異常,都返回true。boolean isCanceller()
:如果任務完成前被取消,則返回true。boolean cancel(boolean mayInterruptRunning)
:- 如果任務還沒開始,執行
cancel(...)
方法將返回false
; - 如果任務已經啟動,執行
cancel(true)
方法將以中斷執行此任務線程的方式來試圖停止任務,如果停止成功,返回true
;當任務已經啟動,執行cancel(false)
方法將不會對正在執行的任務線程產生影響(讓線程正常執行到完成),此時返回false
; - 當任務已經完成,執行
cancel(...)
方法將返回false
。mayInterruptRunning
參數表示是否中斷執行中的線程。
- 如果任務還沒開始,執行
通過方法分析我們也知道實際上Future提供了3種功能:
- (1)能夠中斷執行中的任務
- (2)判斷任務是否執行完成
- (3)獲取任務執行完成后額結果。
示例
我們通過簡單的例子來體會使用Callable和Future來獲取任務結果的用法。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CalBFutrueTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
final Future<String> submit = newCachedThreadPool.submit(new TaskCallback());
System.out.println(Thread.currentThread().getName()+"-主線程開始執行");
new Thread(new Runnable() {
public void run() {
try {
String result = submit.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("主線程執行任務...");
if(newCachedThreadPool!=null) {
//關閉線程池
newCachedThreadPool.shutdown();
}
}
}
class TaskCallback implements Callable<String> {
public String call() throws Exception {
System.out.println("子線程正在執行任務,請等待5s");
Thread.sleep(5000);
System.out.println("子線程 任務結束");
return "abab";
}
}
執行結果
子線程正在執行任務,請等待5s
main-主線程開始執行
主線程執行任務...
子線程 任務結束
abab
Future模式
Future
模式的核心在於:去除了主函數的等待時間,並使得原本需要等待的時間段可以用於處理其他業務邏輯
Futrure
模式:對於多線程,如果線程A要等待線程B
的結果,那么線程A
沒必要等待B
,直到B
有結果,可以先拿到一個未來的Future
,等B
有結果是再取真實的結果。
在多線程中經常舉的一個例子就是:網絡圖片的下載,剛開始是通過模糊的圖片來代替最后的圖片,等下載圖片的線程下載完圖片后在替換。而在這個過程中可以做一些其他的事情。
首先客戶端向服務器請求RealSubject
,但是這個資源的創建是非常耗時的,怎么辦呢?這種情況下,首先返回Client
一個FutureSubject
,以滿足客戶端的需求,於此同時呢,Future
會通過另外一個Thread
去構造一個真正的資源,資源准備完畢之后,在給future
一個通知。如果客戶端急於獲取這個真正的資源,那么就會阻塞客戶端的其他所有線程,等待資源准備完畢。
公共數據接口,FutureData和RealData都要實現。
public abstract class Data {
public abstract String getRequest();
}
真實數據RealData
public class RealData extends Data {
private String result;
public RealData(String data) {
System.out.println("正在使用data:" + data + "網絡請求數據,耗時操作需要等待.");
try {
Thread.sleep(3000);
} catch (Exception e) {}
System.out.println("操作完畢,獲取結果...");
result = "返回值aaaabb";
}
@Override
public String getRequest() {
return result;
}
}
FutureData,當有線程想要獲取RealData的時候,程序會被阻塞。等到RealData被注入才會使用getReal()方法。
public class FutureData extends Data {
public volatile static boolean ISFLAG = false;
private RealData realData;
public synchronized void setRealData(RealData realData) {
// 如果已經獲取到結果,直接返回
if (ISFLAG) {
return;
}
// 如果沒有獲取到數據,傳遞真是對象
this.realData = realData;
ISFLAG = true;
// 進行通知
notify();
}
@Override
public synchronized String getRequest() {
while (!ISFLAG) {
try {
wait();
} catch (Exception e) {}
}
// 獲取到數據,直接返回
return realData.getRequest();
}
}
FutureClient 客戶端
public class FutureClient {
public Data request(String queryStr) {
FutureData furureData = new FutureData();
new Thread(new Runnable() {
@Override
public void run() {
RealData realData = new RealData(queryStr);
furureData.setRealData(realData);
}
}).start();
return furureData;
}
}
調用者:
public class Main {
public static void main(String[] args) {
FutureClient futureClient = new FutureClient();
Data request = futureClient.request("請求參數.");
System.out.println("請求發送成功!");
System.out.println("執行其他任務...");
String result = request.getRequest();
System.out.println("獲取到結果..." + result);
}
}
執行結果
請求發送成功!
執行其他任務...
正在使用data:請求參數.網絡請求數據,耗時操作需要等待.
操作完畢,獲取結果...
獲取到結果...返回值aaaabb
- 調用者請求資源,
client.request("name");
完成對數據的准備,當要獲取資源的時候,data.getResult()
,如果資源沒有准備好isReady = false
;那么就會阻塞該線程。直到資源獲取然后該線程被喚醒。