通常使用線程池執行任務都是調用的execute方法,這個方法定義在Executor接口中:ide
public interface Executor { void execute(Runnable command); }
這個方法是沒有返回值的,並且只接受Runnable。this
那麼像獲得線程的返回值怎嘛辦呢?spa
在ExecutorService接口中能找到這個方法:線程
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task);
這個方法接收兩種參數,Callable和Runnable。返回值是Future。日誌
下面具體看一下這些是什麼東西。code
Callable和Runnable
先看一下兩個接口的定義:orm
Callableblog
public interface Callable<V> { V call() throws Exception; }
Runnable接口
interface Runnable { public abstract void run(); }
和明顯能看到區別:get
1.Callable能接受一個泛型,而後在call方法中返回一個這個類型的值。而Runnable的run方法沒有返回值
2.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 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()); }
輸出
這個就能抓到異常了。