Java多執行緒-執行緒池ThreadPoolExecutor的submit返回值Future
原文地址 http://blog.csdn.net/qq_25806863/article/details/71214033
一般使用執行緒池執行任務都是呼叫的execute方法,這個方法定義在Executor介面中:
public interface Executor { void execute(Runnable command); }
這個方法是沒有返回值的,而且只接受Runnable。
那麼像得到執行緒的返回值怎嘛辦呢?
在ExecutorService介面中能找到這個方法:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
這個方法接收兩種引數,Callable和Runnable。返回值是Future。
下面具體看一下這些是什麼東西。
Callable和Runnable
先看一下兩個介面的定義:
-
Callable
public interface Callable<V> { V call() throws Exception; }
-
Runnable
interface Runnable { public abstract void run(); }
和明顯能看到區別:
- Callable能接受一個泛型,然後在call方法中返回一個這個型別的值。而Runnable的run方法沒有返回值
- Callable的call方法可以丟擲異常,而Runnable的run方法不會丟擲異常。
Future
返回值Future也是一個介面,通過他可以獲得任務執行的返回值。
定義如下:
public interface Future<V> {
boolean cancel(boolean var1);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long var1, TimeUnit var3) throws InterruptedException, ExecutionException, TimeoutException;
}
其中的get方法獲取的就是返回值。
來個例子
submit(Callable<T> task)
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
//建立一個Callable,3秒後返回String型別
Callable myCallable = new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
System.out.println("calld方法執行了");
return "call方法返回值";
}
};
System.out.println("提交任務之前 "+getStringDate());
Future future = executor.submit(myCallable);
System.out.println("提交任務之後,獲取結果之前 "+getStringDate());
System.out.println("獲取返回值: "+future.get());
System.out.println("獲取到結果之後 "+getStringDate());
}
public static String getStringDate() {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
String dateString = formatter.format(currentTime);
return dateString;
}
}
通過executor.submit提交一個Callable,返回一個Future,然後通過這個Future的get方法取得返回值。
看一下輸出:
提交任務之前 12:13:01
提交任務之後,獲取結果之前 12:13:01
calld方法執行了
獲取返回值: call方法返回值
獲取到結果之後 12:13:04
get()方法的阻塞性
通過上面的輸出可以看到,在呼叫submit提交任務之後,主執行緒本來是繼續運行了。但是執行到future.get()的時候就阻塞住了,一直等到任務執行完畢,拿到了返回的返回值,主執行緒才會繼續執行。
這裡注意一下,他的阻塞性是因為呼叫get()方法時,任務還沒有執行完,所以會一直等到任務完成,形成了阻塞。
任務是在呼叫submit方法時就開始執行了,如果在呼叫get()方法時,任務已經執行完畢,那麼就不會造成阻塞。
下面在呼叫方法前先睡4秒,這時就能馬上得到返回值。
System.out.println("提交任務之前 "+getStringDate());
Future future = executor.submit(myCallable);
System.out.println("提交任務之後 "+getStringDate());
Thread.sleep(4000);
System.out.println("已經睡了4秒,開始獲取結果 "+getStringDate());
System.out.println("獲取返回值: "+future.get());
System.out.println("獲取到結果之後 "+getStringDate());
提交任務之前 12:36:04
提交任務之後 12:36:04
calld方法執行了
已經睡了4秒,開始獲取結果 12:36:08
獲取返回值: call方法返回值
獲取到結果之後 12:36:08
可以看到嗎,因為睡了4秒,任務已經執行完畢,所以get方法立馬就得到了結果。
同樣的原因,submit兩個任務時,總阻塞時間是最長的那個。
例如,有兩個任務,一個3秒,一個5秒。
Callable myCallable = new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(5000);
System.out.println("calld方法執行了");
return "call方法返回值";
}
};
Callable myCallable2 = new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
System.out.println("calld2方法執行了");
return "call2方法返回值";
}
};
System.out.println("提交任務之前 "+getStringDate());
Future future = executor.submit(myCallable);
Future future2 = executor.submit(myCallable2);
System.out.println("提交任務之後 "+getStringDate());
System.out.println("開始獲取第一個返回值 "+getStringDate());
System.out.println("獲取返回值: "+future.get());
System.out.println("獲取第一個返回值結束,開始獲取第二個返回值 "+getStringDate());
System.out.println("獲取返回值2: "+future2.get());
System.out.println("獲取第二個返回值結束 "+getStringDate());
輸出
提交任務之前 14:14:47
提交任務之後 14:14:48
開始獲取第一個返回值 14:14:48
calld2方法執行了
calld方法執行了
獲取返回值: call方法返回值
獲取第一個返回值結束,開始獲取第二個返回值 14:14:53
獲取返回值2: call2方法返回值
獲取第二個返回值結束 14:14:53
獲取第一個結果阻塞了5秒,所以獲取第二個結果立馬就得到了。
submit(Runnable task)
因為Runnable是沒有返回值的,所以如果submit一個Runnable的話,get得到的為null:
Runnable myRunnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " run time: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Future future = executor.submit(myRunnable);
System.out.println("獲取的返回值: "+future.get());
輸出為:
pool-1-thread-1 run time: 1493966762524
獲取的返回值: null
submit(Runnable task, T result)
雖然submit傳入Runnable不能直接返回內容,但是可以通過submit(Runnable task, T result)
傳入一個載體,通過這個載體獲取返回值。這個其實不能算返回值了,是交給執行緒處理一下。
先新建一個載體類Data:
public static class Data {
String name;
String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
然後在Runnable的構造方法中傳入:
static class MyThread implements Runnable {
Data data;
public MyThread(Data name) {
this.data = name;
}
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("執行緒 執行:");
data.setName("新名字");
data.setSex("新性別");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然後呼叫:
Data data = new Data();
Future<Data> future = executor.submit(new MyThread(data), data);
System.out.println("返回的結果 name: " + future.get().getName()+", sex: "+future.get().getSex());
System.out.println("原來的Data name: " + data.getName()+", sex: "+data.getSex());
輸出:
執行緒 執行:
返回的結果 name: 新名字, sex: 新性別
原來的Data name: 新名字, sex: 新性別
發現原來的data也變了。
get(long var1, TimeUnit var3)
前面都是用的get()方法獲取返回值,那麼因為這個方法是阻塞的,有時需要等很久。所以有時候需要設定超時時間。
get(long var1, TimeUnit var3)這個方法就是設定等待時間的。
如下面的任務需要5秒才能返回結果:
Callable myCallable = new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(5000);
return "我是結果";
}
};
使用get:
Future future1 = executor.submit(myCallable);
System.out.println("開始拿結果 "+getStringDate());
System.out.println("返回的結果是: "+future1.get()+ " "+getStringDate());
System.out.println("結束拿結果 "+getStringDate());
輸出是:
開始拿結果 16:00:43
返回的結果是: 我是結果 16:00:48
結束拿結果 16:00:48
現在要求最多等3秒,拿不到返回值就不要了,所以用get(long var1, TimeUnit var3)
這個方法
方法的第一個引數是長整形數字,第二個引數是單位,跟執行緒池ThreadPoolExecutor的構造方法裡一樣的。
Future future1 = executor.submit(myCallable);
System.out.println("開始拿結果 "+getStringDate());
try {
System.out.println("返回的結果是: "+future1.get(3, TimeUnit.SECONDS)+ " "+getStringDate());
} catch (TimeoutException e) {
e.printStackTrace();
System.out.println("超時了 "+getStringDate());
}
System.out.println("結束拿結果 "+getStringDate());
然後輸出是
過了三秒就丟擲超時異常了,主執行緒繼續執行,不會再繼續阻塞。
異常
使用submit方法還有一個特點就是,他的異常可以在主執行緒中catch到。
而使用execute方法執行任務是捕捉不到異常的。
用下面這個Runnable來說,這個 裡面一定會丟擲一個異常
Runnable myRunnable = new Runnable() {
@Override
public void run() {
executor.execute(null);
}
};
使用execute
這裡如果捕捉到異常,只打印一行異常資訊。
try {
executor.execute(myRunnable);
} catch (Exception e) {
e.printStackTrace();
System.out.println("抓到異常 "+e.getMessage());
}
輸出
並沒有出現抓到異常哪行日誌。而且這個異常輸出是線上程pool-1-thread-1
中,並不是在主執行緒中。說明主執行緒的catch不能捕捉到這個異常。
使用submit
try {
Future future1= executor.submit(myCallable);
future1.get();
} catch (Exception e) {
e.printStackTrace();
System.out.println("抓到異常 "+e.getMessage());
}
輸出
這個就能抓到異常了。
作者:喵了個嗚s
連結:https://www.jianshu.com/p/c1d7cdaf2a80
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授