Android多线程-Callable、Future、FutureTask

418 阅读4分钟

Callable

Callable在包java.util.concurrent中,在java 1.5版本加入。

Callable与Runnable

Runnable

当线程执行Runnable时,都是通过其run()执行具体工作,执行完毕后线程结束。若想在执行完成,需要在相应的环节增加回调,或通过CountDownLatch等手段处理。

Callable

Callable的一般是配合ExecutorService使用的,而ExecutorService的常用实现一般为通过Executors的几个静态方法生成的ThreadPoolExecutor实例。

Executors.java
​
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

ThreadPoolExecutor、AbstractExecutorService、ExecutorService关系类图如下 ExecutorService.jpg ExecutorService只是接口,submit具体实现在AbstractExecutorService中,可见无论submit传入的是Callable还是Runnable最后都封装为FutureTask并给execute方法处理同时返回FutureTask。

AbstractExecutorService.java
​
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
​
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
​
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
    
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

FutureTask

FutureTask.jpg FutureTask实现了RunnableFuture接口,RunnableFuture继承自Runnable和Future,所以FutureTask既可以作为Runnable直接运行,又包含了Future中的get、cancel及isXXX方法,可以获取线程状态并做处理。

从FutureTask的两个构造函数可知,传入的Runnable和泛型返回值,会构造成Callable后续流程与其一致。

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

FutureTask有两个关键方法get()和cancel(boolean mayInterruptIfRunning)

get()

注意:调用get方会阻塞直到任务执行结束返回结果为止,在Android主线程上调用会导致主线程block

    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;
​
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
​
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
       .....
        long startTime = 0L;    // Special value 0L means not yet parked
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING)
                // We may have already promised (via isDone) that we are done
                // so never return empty-handed or throw InterruptedException
                Thread.yield();
            else if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
            .......
              LockSupport.park(this);//阻塞当前线程
        }
    }

在get过程中,会自旋判断当前状态当状态值大于COMPLETING时返回(线程中的任务执行完成会将STATE从COMPLETING更新为NORMAL),或者有中断信号则会中断并抛出InterruptedException。返回值为Callable类中call方法的返回值。

cancel(boolean mayInterruptIfRunning)
    public void run() {
      //state不为NEW直接return(如state为CANCEL)
        if (state != NEW ||
            !RUNNER.compareAndSet(this, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            .......
        }
    }
​
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW && STATE.compareAndSet
              (this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    STATE.setRelease(this, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

参考run()的源码,FutureTask的state为NEW时有两种可能

1.新建未执行

2.执行中

根据cancel的参数boolean mayInterruptIfRunning的值不同分为两种情况

  1. mayInterruptIfRunning is true

    如果当前FutureTask的state为NEW,并且任务已经在执行中,则会将state设置为INTERRUPTING,并调用其所在的线程interrupt(),发送中断信号。完成后state置为INTERRUPTED

  2. mayInterruptIfRunning is false

    只是将state的值从NEW改为CANCELLED。后续当此FutureTask开始执行时,在其run()会进行判断,若state不为NEW则退出,此方式更符合整个Future、FutureTask结构的设计

        public void run() {
            if (state != NEW ||
                !RUNNER.compareAndSet(this, null, Thread.currentThread()))
                return;
                .......
    

代码示例

FutureTask以及Future的特殊性主要体现在获取运行中状态和任务的cancel。

示例中创建5个任务并submit到单线程的ThreadPool中执行(模拟排队处理的情况)

    private void cancelFuture(){
        ExecutorService executor = Executors.newFixedThreadPool(1);
        ArrayList<Future<Integer>> list = new ArrayList<>();
​
        for (int i = 0; i < 5; i++) {
            Future<Integer> future = executor.submit(new CallableImpl(i));
            list.add(future);
        }
​
        // 先让第一个任务执行10ms
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // cancel第一个任务
        list.get(0).cancel(false);
        executor.shutdown();
    }
​
    static class CallableImpl implements Callable<Integer> {
        int num;
​
        CallableImpl(int num) {
            this.num = num;
        }
​
        @Override
        public Integer call() throws Exception {
            int val = 0;
            for (int i = 0; i < 10; i++) {
                val++;
                Thread.sleep(10);
            }
​
            System.out.println(String.format("Task %d complete", num));
            return val;
        }
    }

在第0个任务执行过程中,调用其Future的cancel这里分几种情况说明

1.任务运行中,mayInterruptIfRunning为false

private void cancelFuture(){
        ...
        // cancel第一个任务
        list.get(0).cancel(false);
        ...
    }

cancel无效,输出为:

Task 0 complete Task 1 complete Task 2 complete Task 3 complete Task 4 complete

2.任务运行中,mayInterruptIfRunning为true

private void cancelFuture(){
        ...
        // cancel第一个任务
        list.get(0).cancel(true);
        ...
    }

第0个任务被cancel,输出为:

Task 1 complete Task 2 complete Task 3 complete Task 4 complete

第0个未执行完成

3.cancel多个任务,mayInterruptIfRunning为false

private void cancelFuture(){
        ....
        // cancel多个任务
        list.get(0).cancel(false);
        list.get(1).cancel(false);
        list.get(2).cancel(false);
        ....
    }

当mayInterruptIfRunning为false,除第0个运行中的无法cancel,其他被cancel

输出为:

Task 0 complete Task 3 complete Task 4 complete

4.cancel多个任务,mayInterruptIfRunning为true

private void cancelFuture(){
        ...
        // cancel第一个任务
        list.get(0).cancel(true);
        list.get(1).cancel(true);
        list.get(2).cancel(true);
        ...
    }

任务都被停止,输出为

Task 3 complete Task 4 complete

总结

Future接口的实现类为FutureTask,当需要获取task状态时可通过其相应的isDone、isCancelled获得。

FutureTask.cancel(boolean mayInterruptIfRunning),

当参数为false时,只是通过修改FutureTask的state为CANCELLED使得为开始的task取消执行,此种方式较为轻量影响较小。

当参数为true时,修改state为INTERRUPTING,同时向task所在线程发送interrupt()信号,与常规的线程中断相同,线程若可处理中断信号或处于WAITING、TIMED_WAITING都会被中断。此种方式较为生硬但可中断运行中的task.