Android中Callable、Future、FutureTask的概念以及幾種線程池的使用


@author:小馬快跑
@email:[email protected]
@github:https://github.com/crazyqiang


學習線程池必備知識:

在開始介紹線程池之前,先來介紹下Callable和Future的概念,衆所周知,Android中實現多線程的方式有兩種,實現Runnable接口或者繼承一個Thread,但是這兩種方式都有一個缺點:在任務執行完成之後沒有返回結果,所以在Java 1.5之後,出現了Callable和Future,通過他們構建的線程,可以在線程執行完成之後得到返回結果。

先來對比Runnable 和Callable:

Runnable :

public interface Runnable {  
    public abstract void run();  
}  

Callable:

public interface Callable<V> {
  V call() throws Exception;
 }

Runnable 接口中的run()方法的返回值是void,所以Runnable無返回值;而Callable接口中的call()方法的返回值是V,也可以拋出異常,所以Callable是可以有返回值的。接着來看下Future:

Future:

public interface Future<V> {
  boolean cancel(boolean mayInterruptIfRunning);

  boolean isCancelled();

  boolean isDone();

  V get() throws InterruptedException, ExecutionException;

  V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
 }

Future用來獲取異步計算的結果,即是獲取Callable任務的結果

Future 備註
cancel(boolean) 嘗試取消異步任務的執行。如果任務已經執行完成、已經被取消、因爲某種原因不能被取消,則返回false;如果任務正在執行,並且mayInterruptIfRunning爲true,那麼會調用interrupt()嘗試打斷任務。該方法返回結果後,isDone()總會返回true
isCancelled() 如果在任務完成前被取消,返回true
isDone() 如果任務完成則返回true。任務完成包括正常結束、任務被取消、任務發生異常,都返回true
get() 獲取異步任務執行結果,如果沒有返回,則阻塞等待
get(long timeout, TimeUnit unit) 在給定的時間內等待獲取異步任務結果,如果超時還未獲取到結果,則會拋出TimeoutException

Future只是一個接口,還需要看Future的具體實現類:

future.png

以FutureTask爲例,具體分析下FutureTask:

public class FutureTask<V> implements RunnableFuture<V> {
  ................其他.....................
  }

咦?FutureTask並沒有實現Future接口,而是實現了一個叫RunnableFuture的接口,這貨從哪裏蹦出來的?我們點進去看一下:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

吆西!原來這貨不僅繼承了Future接口,還繼承了Runnable接口(PS:接口可以多繼承),那麼我們的FutureTask也就實現了Runnable和Future接口

FutureTask 備註
boolean isCancelled() 同Future
boolean isDone() 同Future
boolean cancel(boolean mayInterruptIfRunning) 同Future
V get() 同Future
V get(long timeout, TimeUnit unit) 同Future
void run() 如果這個任務沒有被取消,將直接在當前線程內執行任務

FutureTask有兩個構造方法:

public FutureTask(Callable<V> callable)
public FutureTask(Runnable runnable, V result)

我們看到FutureTask初始化時不僅可以傳入Callable,還可以傳入一個Runnable和一個”返回結果result”,這裏爲什麼返回結果要加引號呢,來看下源碼就知道了:

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

上面FutureTask()的初始化構造參數中調用了Executors.callable():

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}

private static final class RunnableAdapter<T> implements Callable<T> {
    private final Runnable task;
    private final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
         //把傳入的值直接返回
        return result;
    }
}

通過源碼我們看到,在最後的call()方法中,直接把傳入的值返回了,所以FutureTask(Runnable runnable, V result)得到的只是預設的結果,Runnable並不會得到執行的結果。如果不需要預設的返回結果,可以像下面這樣初始化:

Future<?> f = new FutureTask<Void>(runnable, null)

FutureTask的使用:

 //初始化一個線程池
 ExecutorService executor = = Executors.newSingleThreadExecutor();
 //new 一個Callable並傳入FutureTask
 FutureTask<String> future =new FutureTask<>(new Callable<String>() {
     public String call() {
        //do something
       return result;
   }});
 executor.execute(future);
 //在異步任務執行期間可以做一些其他的事情
 displayOtherThings();
 //通過future.get()得到異步任務執行結果
 String result=future.get();

ExecutorService :

ExecutorService繼承自Executor接口,並提供了管理線程以及創建可以追蹤一個或多個異步任務的進展的Future的方法。

public interface ExecutorService extends Executor {

  //無法提交新任務,但是已經提交的任務將繼續執行,當執行完成後關閉線程池
  void shutdown();
  //嘗試停止所有正在執行的任務,暫停等待任務的處理,並返回等待執行的任務列表。
  List<Runnable> shutdownNow();
  //如果線程池已經關閉則返回true
  boolean isShutdown();
  //如果所有任務都在線程池關閉後完成,則返回true。注意,除非首先調用 shutdown 或 shutdownNow,否則 isTerminated 永不爲 true。
  boolean isTerminated();
  //阻塞等待,直到所有任務在關閉請求後完成執行,或者超時發生,或者當前線程被中斷
  // 如果此執行程序終止,則返回 true;如果終止前超時了,則返回 false 
  boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

  <T> Future<T> submit(Callable<T> task);

  <T> Future<T> submit(Runnable task, T result);
  //提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功 完成時將會返回 null
  Future<?> submit(Runnable task);

  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

  //執行給定的任務,當所有任務完成或超時期滿時(無論哪個首先發生),返回保持任務狀態和結果的 Future 列表。
  //返回列表的所有元素的 Future.isDone() 爲 true。一旦返回後,即取消尚未完成的任務。
  //注意,可以正常地或通過拋出異常來終止已完成 任務。如果此操作正在進行時修改了給定的
  // collection,則此方法的結果是不確定的。
  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, 
             long timeout, TimeUnit unit)
        throws InterruptedException;

  <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
  //執行給定的任務,如果在給定的超時期滿前某個任務已成功完成(也就是未拋出異常),則返回其結果。
  //一旦正常或異常返回後,則取消尚未完成的任務。如果此操作正在進行時修改了給定的 collection,
  //則此方法的結果是不確定的。
  <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
 }

線程池的使用:

使用線程池的好處:
1、當執行大量異步任務時,線程池能提供更好的性能體驗,因爲線程池能減少每個任務的調用開銷,重用存在的線程,減少對象創建、消亡的開銷
2、還可以提供綁定和管理資源 (包括執行任務時使用的線程) 的方法。有效控制最大併發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞
3、 提供定時執行、定期執行、單線程、併發數控制等功能。

Android中提供了四種線程池,四種線程池內部實現都是直接或者間接用的ThreadPoolExecutor

1、Executors.newCachedThreadPool():只有Integer.MAX_VALUE個非核心線程,當有任務來時,如果線程池中的線程都處於活動狀態,那麼會新建線程來執行,否則就會利用空閒線程去執行,空閒線程都會有一個超時機制,超過60秒的空閒線程會被回收。任務隊列爲空集合,所以所有任務都會被立即執行,CachedThreadPool適合執行大量的耗時較少的操作。

效果圖:
CachedThreadPool.gif

因爲非核心線程數有無限個,所以不管有多少任務都可以並行執行,可以看到上述5個任務一起執行,核心代碼:

//初始化線程池
ExecutorService threadPool= Executors.newCachedThreadPool();
//初始化Callable
Callable<Integer> callable = new Callable<Integer>() {
    int num = 0;
    @Override
    public Integer call() throws Exception {
        while (num < 100) {
            num++;
            sendMsg(num, what);
            Thread.sleep(50);
        }
        return 100;
    }
 };
//執行線程任務
threadPool.submit(callable);

2、Executors.newFixedThreadPool(int nThreads):只有核心線程並且不會被回收,任務隊列沒有大小限制。

效果圖:
FixedThreadPool.gif
因爲核心線程數參數我們傳入的是4,可所以看到先執行其中的4個任務,等待有任務執行完成後接着去執行第5個任務,核心代碼:

//初始化線程池
ExecutorService threadPool= Executors.newFixedThreadPool(4);
//初始化Callable
Callable<Integer> callable = new Callable<Integer>() {
    int num = 0;
    @Override
    public Integer call() throws Exception {
        while (num < 100) {
            num++;
            sendMsg(num, what);
            Thread.sleep(50);
        }
        return 100;
    }
 };
//執行線程任務
threadPool.submit(callable);

3、Executors.newSingleThreadExecutor():內部只有一個核心線程,所有任務按順序執行 統一所有任務到一個線程中,使得這些任務不用處理線程同步問題。

效果圖:
SingleThreadExecutor.gif

可以看到每次只能執行一個任務,也就是所有任務都是串行執行的,核心代碼:

//初始化線程池
ExecutorService threadPool= Executors.newSingleThreadExecutor();
//初始化Callable
Callable<Integer> callable = new Callable<Integer>() {
    int num = 0;
    @Override
    public Integer call() throws Exception {
        while (num < 100) {
            num++;
            sendMsg(num, what);
            Thread.sleep(50);
        }
        return 100;
    }
 };
//執行線程任務
threadPool.submit(callable);

4、Executors.newScheduledThreadPool(int corePoolSize): 核心線程是固定的,非核心線程是不固定的,非核心線程閒置時會被立即回收,主要用於執行定時任務和具有週期性的重複任務。

效果圖:
Schedule.gif
ScheduledExecutorService傳入的核心線程數是4,並且是在延遲2秒之後執行的,核心代碼:

//初始化線程池,核心線程數爲4
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(4);
//初始化Callable
Callable<Integer> callable = new Callable<Integer>() {
    int num = 0;
    @Override
    public Integer call() throws Exception {
        while (num < 100) {
            num++;
            sendMsg(num, what);
            Thread.sleep(50);
        }
        return 100;
    }
};
//延遲2秒後執行
scheduledPool.schedule(callable, 2, TimeUnit.SECONDS);

除上述延遲執行的方法外,ScheduledExecutorService中還有下列方法:

public interface ScheduledExecutorService extends ExecutorService {
    //延遲delay(TimeUnit 爲單位)之後執行Runnable 
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
    //延遲delay(TimeUnit 爲單位)之後執行Callable
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);
    //延遲initialDelay之後每period時間執行一次Callable,循環執行
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
     //第一次延遲initialDelay之後執行,之後在每次完成後延遲delay時間執行下一次操作
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
 }

本文例子中完整代碼已上傳github:Android線程池的使用

發佈了53 篇原創文章 · 獲贊 98 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章