大話Android多執行緒(四) Callable、Future和FutureTask

Anlia發表於2019-03-04

版權宣告:本文為博主原創文章,未經博主允許不得轉載
原始碼:github.com/AnliaLee
大家要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論

前言

大話Android多執行緒(一) 一文中,我們聊了建立執行緒的兩種方式(繼承Thread和實現Runnable介面),並比對了它們的區別。本章我們將介紹第三種方式 —— 通過實現Callable介面來建立執行緒

往期回顧
大話Android多執行緒(一) Thread和Runnable的聯絡和區別
大話Android多執行緒(二) synchronized使用解析
大話Android多執行緒(三) 執行緒間的通訊機制之Handler


實現Callable介面建立執行緒

我們先簡單舉個栗子,看看通過實現Callable介面來建立執行緒的方式和之前兩種有什麼區別

某日,高鐵站前,老C和他兒子道別,兒子:“爸爸,你走吧。”老C望了望路邊的小攤,說道

大話Android多執行緒(四) Callable、Future和FutureTask

說完老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){
        }
    }
}
複製程式碼

正常買到橘子的執行結果如下

大話Android多執行緒(四) Callable、Future和FutureTask

如果沒買到橘子呢(我們嘗試在call()方法中丟擲異常,然後在呼叫get()方法時進行捕獲)?

大話Android多執行緒(四) Callable、Future和FutureTask

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());//驗證主執行緒的執行情況
	}
}
複製程式碼

大話Android多執行緒(四) Callable、Future和FutureTask

另外需要注意的是,爸爸的錢只夠買一袋橘子(任務只能執行一次)

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());//驗證主執行緒的執行情況
	}
}
複製程式碼

大話Android多執行緒(四) Callable、Future和FutureTask

總結上面的案例:

  • Callable在被執行緒執行後,可以提供一個返回值,我們可以通過Futureget()方法拿到這個值

Future是一個介面,而FutureTask實現了RunnableFuture介面,RunnableFuture繼承了Runnable介面和Future介面(繼承關係見下圖)

大話Android多執行緒(四) Callable、Future和FutureTask

FutureTask用於非同步獲取執行結果或取消執行任務的場景,它的主要功能有:

  • 可以判斷任務是否完成
  • 可以獲取任務執行結果
  • 可以中斷任務

更詳細的原始碼解析及用法可以看下這幾篇部落格
Java併發程式設計:Callable、Future和FutureTask原理解析
Java FutureTask 原始碼分析 Android上的實現
FutureTask的用法及兩種常用的使用場景

  • Callablecall()方法可以丟擲異常,我們可以在嘗試執行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());//驗證主執行緒的執行情況
	}
}
複製程式碼

那麼執行緒池又是啥?留到下一章我們再“大話”一番吧

本篇部落格到此結束,若大家有啥疑問或建議歡迎留言評論,感激不盡。如果覺得寫得還不錯麻煩點個贊,你們的支援是我最大的動力~

相關文章