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/
并發(fā)的學(xué)習(xí)與使用系列?第四篇
在Java中,開啟一個(gè)線程的唯一方式是,是通過Thread的start方法,并且在線程中執(zhí)行的Runnable的run方法。無論是線程池還是接下來要介紹的Callable,Future還是線程池,最核心最根本的還是調(diào)用到Thread.start()–>Runnable.run(),其他的類的出現(xiàn)可以認(rèn)為是更方便的使用Thread和Runnable,以此為核心會(huì)更容易理解Java的并發(fā)框架。
雖然Thread和Runnable類使得多線程編程簡(jiǎn)單直接,但有一個(gè)缺陷就是:在執(zhí)行完任務(wù)之后無法獲取執(zhí)行結(jié)果。如果需要獲取執(zhí)行結(jié)果,就必須通過共享變量或者使用線程通信的方式來達(dá)到效果,這樣使用起來就比較麻煩。因此從Jdk1.5開始,有了一系列的類的出現(xiàn)來解決這些問題,如Callable和Future,FutureTask以及下篇要講到的線程池從使用到原理學(xué)習(xí)Java線程池。
而自從Java 1.5開始,就提供了Callable和Future以及FutureTask,通過它們可以在任務(wù)執(zhí)行完畢之后得到任務(wù)執(zhí)行結(jié)果。
實(shí)現(xiàn)原理
Thread和Runnable
首先看Thread和Runnable的實(shí)現(xiàn)多線程任務(wù)的原理。
以下是簡(jiǎn)化后的代碼,為了方便理解。
| 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; //省略其他初始化線程的任務(wù) } public void start() { nativeCreate(this, stackSize, daemon);//native方法開啟多線程,并調(diào)用run方法 } public void run() { if (target != null) { target.run(); } } } |
可以看出target是一個(gè)Runnble對(duì)象,通過一個(gè)典型的裝飾模式來擴(kuò)展Runnable,如果不傳入,默認(rèn)為null,需要自己實(shí)現(xiàn)run方法來在新線程里執(zhí)行任務(wù),否則線程不會(huì)做任何事情就結(jié)束。所以無論怎么變化,最終都是Thread的start方法開啟新線程,run方法在這個(gè)新開啟的線程里執(zhí)行任務(wù),當(dāng)然run方法也可以單獨(dú)調(diào)用,但所在線程是調(diào)用者的線程。
裝飾者模式的典型特點(diǎn):裝飾后的類和被裝飾的類,類型不變(繼承Runnable),提供新的行為,方法(start()等),關(guān)于設(shè)計(jì)模式的詳細(xì)細(xì)節(jié),見常見的設(shè)計(jì)模式解讀。
Callable和Future,FutureTask
先通過UML圖來看它們和Thrad,Runnable之間的關(guān)系:
Callable與Runnable的功能大致相似,Callable中有一個(gè)call()函數(shù),但是call()函數(shù)有返回值,而Runnable的run()函數(shù)不能將結(jié)果返回給客戶程序。
Future就是對(duì)Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消、查詢是否完成、獲取結(jié)果、設(shè)置結(jié)果操作。其中的get()方法就是用來得到Callable的call()結(jié)果的。
FutureTask是Future的具體實(shí)現(xiàn)類,實(shí)現(xiàn)了get()等方法來對(duì)控制Callabel的行為,又因?yàn)門hread只能執(zhí)行Runnable,所以FutureTask實(shí)現(xiàn)了Runnable接口。
因?yàn)镕utureTask需要在Thread中執(zhí)行,所以需要在run()方法中完成具體的實(shí)現(xiàn):
| 1 2 3 4 5 6 7 8 9 10 | //簡(jiǎn)化后的代碼,為了方便理解 public void run() { Callable<V> c = callable; if (c != null && state == NEW) { V result; result = c.call(); set(result); } } |
通過get方法來獲取結(jié)果,get()是個(gè)阻塞方法,直到結(jié)果返回,或者中斷發(fā)生。還可以通過get(long timeout, TimeUnit unit)方法控制等待結(jié)果的最大時(shí)間。
| 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方法實(shí)際的任務(wù)是在Callable的call中完成,FutureTask的實(shí)現(xiàn)方式采用了適配器模式來完成。
如果構(gòu)造函數(shù)傳入的是Runnable,則通過Executors的靜態(tài)函數(shù)callable(Runnable task,…)將Runnable轉(zhuǎn)換為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); } |
適配器模式的典型特點(diǎn):包裝另一個(gè)對(duì)象(包裝了Callable),提供不同的接口(Runnable接口)
Callable和Future,FutureTask經(jīng)常容易讓人記憶混亂,理解后就知道了其實(shí)Future和FutureTask就是用來將Callable包裝成一個(gè)Runnable,這樣才能夠在Thread中執(zhí)行,同時(shí)提供將結(jié)果返回的功能,三個(gè)類總是同時(shí)出現(xiàn),整體理解為是一個(gè)可以得到返回結(jié)果的Runnable。
使用
那么怎么使用這些類呢呢?由于FutureTask實(shí)現(xiàn)了Runnable,因此它既可以通過Thread包裝來直接執(zhí)行,也可以提交給ExecuteService來執(zhí)行,在Thread中,就像使用Runnable一樣。
關(guān)于線程池的更多細(xì)節(jié)將在下一篇文章中進(jìn)行講解
示例代碼如下:
| 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運(yùn)行結(jié)果" + 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 { // 這是在后臺(tái)線程 System.out.println(Thread.currentThread().getName()+"::useThread運(yùn)行結(jié)果" + get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }; Thread thread = new Thread(futureTask); thread.start(); try { //這是在主線程,會(huì)阻塞 System.out.println(Thread.currentThread().getName()+"::useThread運(yùn)行結(jié)果" + 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; } } } |
結(jié)果:
pool-1-thread-1 main::useExecutor運(yùn)行結(jié)果499500 Thread-0 main::useThread運(yùn)行結(jié)果124750 Thread-0::useThread運(yùn)行結(jié)果124750FutureTask.get()是阻塞的,useExecutor()和useThread()也會(huì)阻塞。這里只是說明FutureTask.get()所在的線程是調(diào)用者所在的線程,在Android中使用的話,一般是在FutureTask的done方法中g(shù)et,這時(shí)get就是在后臺(tái)線程調(diào)用了,然后通過Handler通知到UI或其他線程。我寫了一個(gè)AysncTask替代庫(kù)AsyncTaskScheduler,實(shí)現(xiàn)了通過線程池調(diào)用和單個(gè)線程調(diào)用的具體方式,里面有具體的實(shí)現(xiàn)方式。
轉(zhuǎn)載于:https://www.cnblogs.com/bigben0123/p/8243720.html
總結(jié)
以上是生活随笔為你收集整理的Callable和Future、FutureTask的使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 查看mysql 默认端口号和修改端口号
- 下一篇: Python:名片管理系统