版權宣告:本文為博主原創文章,未經博主允許不得轉載
原始碼:github.com/AnliaLee
大家要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論
前言
在 大話Android多執行緒(一) 一文中,我們聊了建立執行緒的兩種方式(繼承Thread和實現Runnable介面),並比對了它們的區別。本章我們將介紹第三種方式 —— 通過實現Callable介面來建立執行緒
往期回顧
大話Android多執行緒(一) Thread和Runnable的聯絡和區別
大話Android多執行緒(二) synchronized使用解析
大話Android多執行緒(三) 執行緒間的通訊機制之Handler
實現Callable介面建立執行緒
我們先簡單舉個栗子,看看通過實現Callable介面來建立執行緒的方式和之前兩種有什麼區別
某日,高鐵站前,老C和他兒子道別,兒子:“爸爸,你走吧。”老C望了望路邊的小攤,說道
說完老C便走去小攤買橘子了(實現Callable介面,重寫call()方法)
public static class TestCallable implements Callable{
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + ":我買幾個橘子去。你就在此地,不要走動" + " 時間:" + getTime());
Thread.sleep(2000);//模擬買橘子的時間
return Thread.currentThread().getName() + ":我買完橘子回來了" + " 時間:" + getTime();
}
}
複製程式碼
兒子自然是乖乖站在原地等爸爸買橘子
public class CallableTest {
//省略部分程式碼...
public static void main(String args[]){
TestCallable callable = new TestCallable();
FutureTask<String> futureTask = new FutureTask<String>(callable);
Thread thread1 = new Thread(futureTask, "爸爸");
thread1.start();
System.out.println("兒子還沒收到橘子" + " 時間:" + getTime());//驗證主執行緒的執行情況
try{
System.out.println(futureTask.get());
System.out.println("兒子收到橘子" + " 時間:" + getTime());//驗證主執行緒的執行情況
}catch (InterruptedException | ExecutionException e){
}
}
}
複製程式碼
正常買到橘子的執行結果如下
如果沒買到橘子呢(我們嘗試在call()方法中丟擲異常,然後在呼叫get()方法時進行捕獲)?
public static class TestCallable implements Callable{
private int ticket = 10;
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + ":我買幾個橘子去。你就在此地,不要走動" + " 時間:" + getTime());
Thread.sleep(2000);//模擬買橘子的時間
System.out.println(Thread.currentThread().getName() + ":橘子賣完了" + " 時間:" + getTime());
throw new NullPointerException("橘子賣完了");
}
}
public static void main(String args[]){
TestCallable callable = new TestCallable();
FutureTask<String> futureTask = new FutureTask<String>(callable);
Thread thread1 = new Thread(futureTask, "爸爸");
thread1.start();
System.out.println("兒子站在原地" + " 時間:" + getTime());//驗證主執行緒的執行情況
try{
System.out.println(futureTask.get());
System.out.println("兒子收到橘子" + " 時間:" + getTime());//驗證主執行緒的執行情況
}catch (InterruptedException | ExecutionException e){
System.out.println("兒子沒收到橘子" + " 時間:" + getTime());//驗證主執行緒的執行情況
}
}
複製程式碼
另外需要注意的是,爸爸的錢只夠買一袋橘子(任務只能執行一次)
public static void main(String args[]){
TestCallable callable = new TestCallable();
FutureTask<String> futureTask = new FutureTask<String>(callable);
Thread thread1 = new Thread(futureTask, "爸爸去了第一個攤位");
Thread thread2 = new Thread(futureTask, "爸爸去了第二個攤位");
Thread thread3 = new Thread(futureTask, "爸爸去了第三個攤位");
thread1.start();
thread2.start();
thread3.start();
System.out.println("兒子站在原地" + " 時間:" + getTime());//驗證主執行緒的執行情況
try{
System.out.println(futureTask.get());
System.out.println("兒子收到橘子" + " 時間:" + getTime());//驗證主執行緒的執行情況
}catch (InterruptedException | ExecutionException e){
System.out.println("兒子沒收到橘子" + " 時間:" + getTime());//驗證主執行緒的執行情況
}
}
複製程式碼
總結上面的案例:
- Callable在被執行緒執行後,可以提供一個返回值,我們可以通過Future的get()方法拿到這個值
Future是一個介面,而FutureTask實現了RunnableFuture介面,RunnableFuture繼承了Runnable介面和Future介面(繼承關係見下圖)
FutureTask用於非同步獲取執行結果或取消執行任務的場景,它的主要功能有:
- 可以判斷任務是否完成
- 可以獲取任務執行結果
- 可以中斷任務
更詳細的原始碼解析及用法可以看下這幾篇部落格
Java併發程式設計:Callable、Future和FutureTask原理解析
Java FutureTask 原始碼分析 Android上的實現
FutureTask的用法及兩種常用的使用場景
- Callable的call()方法可以丟擲異常,我們可以在嘗試執行get()方法時捕獲這個異常
區別於實現Runnable介面建立執行緒的方式,以上這兩點功能Runnable就無法實現了
- FutureTask可以確保任務只執行一次
- 我們在某條執行緒執行get()方法時,該執行緒會被阻塞,直到Future拿到Callable.call()方法的返回值
在UI執行緒中使用時(尤其是後續還有更新UI的操作)要特別注意這點,以免造成介面卡頓。那麼要如何處理這種多執行緒執行耗時任務,等待結果,然後再更新UI的情況呢?沒錯!就是使用我們上一章講到的Handler,Android系統提供的AsyncTask也正是用到了這一方式實現了非同步操作,我們將在後續的章節詳細介紹AsyncTask
此外,除了直接new一個Thread,我們還可以利用執行緒池結合Callable執行多執行緒任務
public static void main(String args[]){
TestCallable callable = new TestCallable();
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(callable);
//或者
//FutureTask<String> future = new FutureTask<String>(callable);
//executor.execute(future);
System.out.println("兒子站在原地" + " 時間:" + getTime());//驗證主執行緒的執行情況
try{
System.out.println(future.get());
System.out.println("兒子收到橘子" + " 時間:" + getTime());//驗證主執行緒的執行情況
}catch (InterruptedException | ExecutionException e){
System.out.println("兒子沒收到橘子" + " 時間:" + getTime());//驗證主執行緒的執行情況
}
}
複製程式碼
那麼執行緒池又是啥?留到下一章我們再“大話”一番吧
本篇部落格到此結束,若大家有啥疑問或建議歡迎留言評論,感激不盡。如果覺得寫得還不錯麻煩點個贊,你們的支援是我最大的動力~