大話Android多線程(四) Callable、Future和FutureTask

版權聲明:本文爲博主原創文章,未經博主容許不得轉載
源碼:github.com/AnliaLee
你們要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論html

前言

大話Android多線程(一) 一文中,咱們聊了建立線程的兩種方式(繼承Thread和實現Runnable接口),並比對了它們的區別。本章咱們將介紹第三種方式 —— 經過實現Callable接口來建立線程java

往期回顧
大話Android多線程(一) Thread和Runnable的聯繫和區別
大話Android多線程(二) synchronized使用解析
大話Android多線程(三) 線程間的通訊機制之Handlerandroid


實現Callable接口建立線程

咱們先簡單舉個栗子,看看經過實現Callable接口來建立線程的方式和以前兩種有什麼區別git

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

說完老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()方法時進行捕獲)?app

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在被線程執行後,能夠提供一個返回值,咱們能夠經過Futureget()方法拿到這個值

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

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

那麼線程池又是啥?留到下一章咱們再「大話」一番吧

本篇博客到此結束,若你們有啥疑問或建議歡迎留言評論,感激涕零。若是以爲寫得還不錯麻煩點個贊,大家的支持是我最大的動力~

相關文章
相關標籤/搜索