Callable和Future、FutureTask的使用
http://www.silencedut.com/2016/06/15/Callable%E5%92%8CFuture%E3%80%81FutureTask%E7%9A%84%E4%BD%BF%E7%94%A8/
并發的學習與使用系列?第四篇
在Java中,開啟一個線程的唯一方式是,是通過Thread的start方法,并且在線程中執行的Runnable的run方法。無論是線程池還是接下來要介紹的Callable,Future還是線程池,最核心最根本的還是調用到Thread.start()–>Runnable.run(),其他的類的出現可以認為是更方便的使用Thread和Runnable,以此為核心會更容易理解Java的并發框架。
雖然Thread和Runnable類使得多線程編程簡單直接,但有一個缺陷就是:在執行完任務之后無法獲取執行結果。如果需要獲取執行結果,就必須通過共享變量或者使用線程通信的方式來達到效果,這樣使用起來就比較麻煩。因此從Jdk1.5開始,有了一系列的類的出現來解決這些問題,如Callable和Future,FutureTask以及下篇要講到的線程池從使用到原理學習Java線程池。
而自從Java 1.5開始,就提供了Callable和Future以及FutureTask,通過它們可以在任務執行完畢之后得到任務執行結果。
實現原理
Thread和Runnable
首先看Thread和Runnable的實現多線程任務的原理。
以下是簡化后的代碼,為了方便理解。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class Thread implements Runnable { Runnable target; public Thread(Runnable runnable) { target = Runnable; //省略其他初始化線程的任務 } public void start() { nativeCreate(this, stackSize, daemon);//native方法開啟多線程,并調用run方法 } public void run() { if (target != null) { target.run(); } } } |
可以看出target是一個Runnble對象,通過一個典型的裝飾模式來擴展Runnable,如果不傳入,默認為null,需要自己實現run方法來在新線程里執行任務,否則線程不會做任何事情就結束。所以無論怎么變化,最終都是Thread的start方法開啟新線程,run方法在這個新開啟的線程里執行任務,當然run方法也可以單獨調用,但所在線程是調用者的線程。
裝飾者模式的典型特點:裝飾后的類和被裝飾的類,類型不變(繼承Runnable),提供新的行為,方法(start()等),關于設計模式的詳細細節,見常見的設計模式解讀。
Callable和Future,FutureTask
先通過UML圖來看它們和Thrad,Runnable之間的關系:
Callable與Runnable的功能大致相似,Callable中有一個call()函數,但是call()函數有返回值,而Runnable的run()函數不能將結果返回給客戶程序。
Future就是對Callable任務的執行結果進行取消、查詢是否完成、獲取結果、設置結果操作。其中的get()方法就是用來得到Callable的call()結果的。
FutureTask是Future的具體實現類,實現了get()等方法來對控制Callabel的行為,又因為Thread只能執行Runnable,所以FutureTask實現了Runnable接口。
因為FutureTask需要在Thread中執行,所以需要在run()方法中完成具體的實現:
| 1 2 3 4 5 6 7 8 9 10 | //簡化后的代碼,為了方便理解 public void run() { Callable<V> c = callable; if (c != null && state == NEW) { V result; result = c.call(); set(result); } } |
通過get方法來獲取結果,get()是個阻塞方法,直到結果返回,或者中斷發生。還可以通過get(long timeout, TimeUnit unit)方法控制等待結果的最大時間。
| 1 2 3 4 5 6 | public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L);//阻塞等待 return report(s); } |
可以看出FutureTask的run方法實際的任務是在Callable的call中完成,FutureTask的實現方式采用了適配器模式來完成。
如果構造函數傳入的是Runnable,則通過Executors的靜態函數callable(Runnable task,…)將Runnable轉換為Callable類型:
| 1 2 3 4 5 6 | public static Callable callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter(task, result); } |
適配器模式的典型特點:包裝另一個對象(包裝了Callable),提供不同的接口(Runnable接口)
Callable和Future,FutureTask經常容易讓人記憶混亂,理解后就知道了其實Future和FutureTask就是用來將Callable包裝成一個Runnable,這樣才能夠在Thread中執行,同時提供將結果返回的功能,三個類總是同時出現,整體理解為是一個可以得到返回結果的Runnable。
使用
那么怎么使用這些類呢呢?由于FutureTask實現了Runnable,因此它既可以通過Thread包裝來直接執行,也可以提交給ExecuteService來執行,在Thread中,就像使用Runnable一樣。
關于線程池的更多細節將在下一篇文章中進行講解
示例代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | /** * Created by SilenceDut on 16/7/15. **/ public class FutureTest { public static void main(String[] args) { FutureTest futureTest = new FutureTest(); futureTest.useExecutor(); futureTest.useThread(); } private void useExecutor() { SumTask sumTask = new SumTask(1000); ExecutorService executor = Executors.newCachedThreadPool(); FutureTask<Integer> futureTask = new FutureTask<Integer>(sumTask); executor.submit(futureTask); executor.shutdown(); try { System.out.println(Thread.currentThread().getName()+"::useExecutor運行結果" + futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } private void useThread() { SumTask sumTask = new SumTask(500); FutureTask<Integer> futureTask = new FutureTask<Integer>(sumTask) { protected void done() { super.done(); try { // 這是在后臺線程 System.out.println(Thread.currentThread().getName()+"::useThread運行結果" + get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }; Thread thread = new Thread(futureTask); thread.start(); try { //這是在主線程,會阻塞 System.out.println(Thread.currentThread().getName()+"::useThread運行結果" + futureTask.get().getName()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } class SumTask implements Callable<Integer> { int number; public SumTask(int num) { this.number = num; } public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()); Thread.sleep(5000); int sum = 0; for (int i = 0; i < number; i++) { sum += i; } return sum; } } } |
結果:
pool-1-thread-1 main::useExecutor運行結果499500 Thread-0 main::useThread運行結果124750 Thread-0::useThread運行結果124750FutureTask.get()是阻塞的,useExecutor()和useThread()也會阻塞。這里只是說明FutureTask.get()所在的線程是調用者所在的線程,在Android中使用的話,一般是在FutureTask的done方法中get,這時get就是在后臺線程調用了,然后通過Handler通知到UI或其他線程。我寫了一個AysncTask替代庫AsyncTaskScheduler,實現了通過線程池調用和單個線程調用的具體方式,里面有具體的實現方式。
轉載于:https://www.cnblogs.com/bigben0123/p/8243720.html
總結
以上是生活随笔為你收集整理的Callable和Future、FutureTask的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 查看mysql 默认端口号和修改端口号
- 下一篇: Python:名片管理系统