美文网首页Android TechAndroid自定义views
Android中Callable、Future、FutureTa

Android中Callable、Future、FutureTa

作者: 饱醉豚我去年买了个表 | 来源:发表于2017-07-27 14:59 被阅读483次

    @author:小马快跑
    @email:mqcoder90@gmail.com
    @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<V>:

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

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

    Future<V>:

    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<V>用来获取异步计算的结果,即是获取Callable<V>任务的结果

    Future 备注
    cancel(boolean) 尝试取消异步任务的执行。如果任务已经执行完成、已经被取消、因为某种原因不能被取消,则返回false;如果任务正在执行,并且mayInterruptIfRunning为true,那么会调用interrupt()尝试打断任务。该方法返回结果后,isDone()总会返回true
    isCancelled() 如果在任务完成前被取消,返回true
    isDone() 如果任务完成则返回true。任务完成包括正常结束、任务被取消、任务发生异常,都返回true
    get() 获取异步任务执行结果,如果没有返回,则阻塞等待
    get(long timeout, TimeUnit unit) 在给定的时间内等待获取异步任务结果,如果超时还未获取到结果,则会抛出TimeoutException

    Future<V>只是一个接口,还需要看Future的具体实现类:

    future.png

    以FutureTask为例,具体分析下FutureTask<V>:

    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线程池的使用

    相关文章

      网友评论

        本文标题:Android中Callable、Future、FutureTa

        本文链接:https://www.haomeiwen.com/subject/mozkkxtx.html