(十) ExecutorService

前言:上一篇FutureTask的执行过程中其实与ExecutorService有关,但是由于对这个知识点不是很熟,所以就略过了,现在专门就这个知识点学习一下。


参考博客:Java线程池 ExecutorService

demo地址:点击打开链接



1. ExecutorService的简单使用

简单地看了下ExecutorService的API,感觉还是适合一边看源码一边写例子比较靠谱,一上来就写个简单的例子热身一下。

package com.example.demo_10_executorservice;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "simpleTest";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future future = executorService.submit(new Callable() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for(int i = 0; i< 10; i++){
                    sum += i;
                }
                return sum;
            }
        });
        try {
            Log.d(TAG, "the sum is: " + future.get());
        }catch (InterruptedException | ExecutionException ex){
            ex.printStackTrace();
        }
        executorService.shutdown();
    }
}

简单来讲就是先新建一个大小为5的线程池,然后在里面执行一个计算0到10累加的任务,执行完毕以后将线程池关闭。(线程池建立看了下适合下一篇博客再写)


2. ExecutorService的继承关系

先拷贝一个总体上继承关系的图

(十) ExecutorService_第1张图片

本博客主要讲的ExecutorService就是继承的Executor接口,而ExecutorService本身自己也是一个接口。


2.1 Executor

先看下Executor:

* The {@code Executor} implementations provided in this package
 * implement {@link ExecutorService}, which is a more extensive
 * interface.  The {@link ThreadPoolExecutor} class provides an
 * extensible thread pool implementation. The {@link Executors} class
 * provides convenient factory methods for these Executors.
 *
 * 

Memory consistency effects: Actions in a thread prior to * submitting a {@code Runnable} object to an {@code Executor} * happen-before * its execution begins, perhaps in another thread. * * @since 1.5 * @author Doug Lea */ public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {@code Executor} implementation. * * @param command the runnable task * @throws RejectedExecutionException if this task cannot be * accepted for execution * @throws NullPointerException if command is null */ void execute(Runnable command); }



从上面注释可以看出如下几点:

  1. ExecutorService是Executor更充分的实现
  2. Executors为线程池提供了便捷的工厂类(与Executor有关,但不是一回事)
  3. 一致性原则:任务提交总是在执行之前
  4. Executor只有void execute(Runnable command)一个方法


2.2 ExecutorService

ExecutorService本身是一个接口,它继承了Executor接口,并在此基础上进行了拓展。

public interface ExecutorService extends Executor
api接口如下图所示

(十) ExecutorService_第2张图片


3.  API分析及实际使用

本文主要还是就ExecutorService的api进行学习,但是由于它只是个接口,所以不可避免选择一个具体的实现进行接口的实际使用,所以还是很粗糙很不全面的演示,本文主要是用的newFixedThreadPool;最好还是后面学习Executors及其生成的各种线程池再来回顾一下。


3.1 submit

submit方法主要就是用来提交单个任务执行的,并且返回一个代表任务执行结果的Future对象,可以调用Future的get()方法获取任务的执行结果。

 /**
     * Submits a value-returning task for execution and returns a
     * Future representing the pending results of the task. The
     * Future's {@code get} method will return the task's result upon
     * successful completion.
     *
     * 

* If you would like to immediately block waiting * for a task, you can use constructions of the form * {@code result = exec.submit(aCallable).get();} * *

Note: The {@link Executors} class includes a set of methods * that can convert some other common closure-like objects, * for example, {@link java.security.PrivilegedAction} to * {@link Callable} form so they can be submitted. * * @param task the task to submit * @param the type of the task's result * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ Future submit(Callable task); /** * Submits a Runnable task for execution and returns a Future * representing that task. The Future's {@code get} method will * return the given result upon successful completion. * * @param task the task to submit * @param result the result to return * @param the type of the result * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ Future submit(Runnable task, T result); /** * Submits a Runnable task for execution and returns a Future * representing that task. The Future's {@code get} method will * return {@code null} upon successful completion. * * @param task the task to submit * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ Future submit(Runnable task);

从上面的源码可以看出几点不一样的细节:

  1. 如果想立刻阻塞等待任务执行结果可以调用result = exec.submit(aCallable).get()
  2. Future submit(Runnable task, T result)已将任务结果写死了,不能像Callable一样返回result
  3. Future submit(Runnable task)执行成功后返回的结果是Null

对应两个demo,源码即运行结果如下:

private void submit2(){
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future future = executorService.submit(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "do something");
            }
        }, "jiatai happy");
        try {
            Log.d(TAG, "the result of submit2 is: " + future.get());
        }catch (InterruptedException | ExecutionException ex){
            ex.printStackTrace();
        }
        executorService.shutdown();
    }

    private void submit3(){
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future future = executorService.submit(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "do something");
            }
        });
        try {
            Log.d(TAG, "the result of submit3 is: " + future.get());
        }catch (InterruptedException | ExecutionException ex){
            ex.printStackTrace();
        }
        executorService.shutdown();
    }

运行结果:

03-11 11:07:37.740 6413-6413/com.example.demo_10_executorservice D/simpleTest: the result of submit1 is: 45
03-11 11:07:37.750 6413-6453/com.example.demo_10_executorservice D/simpleTest: do something
03-11 11:07:37.750 6413-6413/com.example.demo_10_executorservice D/simpleTest: the result of submit2 is: jiatai happy
03-11 11:07:37.752 6413-6459/com.example.demo_10_executorservice D/simpleTest: do something
03-11 11:07:37.752 6413-6413/com.example.demo_10_executorservice D/simpleTest: the result of submit3 is: null


3.2 shutdown and shutdownNow

当我们使用完成ExecutorService之后应该关闭它,否则它里面的线程会一直处于运行状态。

举个例子,如果的应用程序是通过main()方法启动的,在这个main()退出之后,如果应用程序中的ExecutorService没有关闭,这个应用将一直运行。之所以会出现这种情况,是因为ExecutorService中运行的线程会阻止JVM关闭。

如果要关闭ExecutorService中执行的线程,我们可以调用ExecutorService.shutdown()方法。在调用shutdown()方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。

如果我们想立即关闭ExecutorService,我们可以调用ExecutorService.shutdownNow()方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。

复制粘贴了一段,大概意思是说如果没有调用shutdown,有可能会导致内存泄露。

源码如下所示:

/**
     * Initiates an orderly shutdown in which previously submitted
     * tasks are executed, but no new tasks will be accepted.
     * Invocation has no additional effect if already shut down.
     *
     * 

This method does not wait for previously submitted tasks to * complete execution. Use {@link #awaitTermination awaitTermination} * to do that. */ void shutdown(); /** * Attempts to stop all actively executing tasks, halts the * processing of waiting tasks, and returns a list of the tasks * that were awaiting execution. * *

This method does not wait for actively executing tasks to * terminate. Use {@link #awaitTermination awaitTermination} to * do that. * *

There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. For example, typical * implementations will cancel via {@link Thread#interrupt}, so any * task that fails to respond to interrupts may never terminate. * * @return list of tasks that never commenced execution */ List shutdownNow();


我对其他博客上的说法还是心存怀疑的,比较信服源码上的注释和我自己测试的结果,从上面的注释可以看出:

  1. shutdown原来是初始化一个有序的对已提交任务的关闭,但是不接受新的任务,如果线程池已经关闭了再次调用将没有影响
  2. 如果想要等待先前提及但还没执行完毕的任务,那么请调用awaitTermination
  3. shutdownNow会尝试停止所有活跃的正在执行的任务,暂停正在等待的任务,并且返回一个等待执行任务的集合。它不会等正在执行的任务完成。并且也不保证会停止正在执行的任务,和interrupt有异曲同工之妙。


下面就是写demo一个一个验证了。

3.2.1 验证shutdown

shutdown原来是初始化一个有序的对已提交任务的关闭,但是不接受新的任务,如果线程池已经关闭了再次调用将没有影响

shutdown自然会遇到4种情况

  1. 已经执行完的任务
  2. 正在执行的任务
  3. 正在等待执行的任务
  4. 已经调用shutdown后才提交的任务

所以写demo的时候最好是充分地考虑这种情况,API中对这四种情况说得不是很清楚。就我猜测:

  1. 对已经执行完的任务没有影响
  2. 对于正在执行的任务会等待任务执行完毕
  3. 对于正在等待执行的任务会取消执行
  4. 已经调用shutdown后才提及的任务是提交不了的

对应demo(task1 2 3 4分别和四种情况相对应):

private void shutdown1(){
        Log.d(TAG, "----shutdown1 begin---- ");
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Log.d(TAG, "----task1 begin---- ");
        Future future1 = executorService.submit(new Task1());

        Log.d(TAG, "----task2 begin---- ");
        Future future2 = executorService.submit(new Task2());

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.d(TAG, "----task3 begin---- ");
        Future future3 = executorService.submit(new Task3());

        executorService.shutdown();
        Log.d(TAG, "----executorService.shutdown---- ");

        Future future4 = null;
        try {
            future4 = executorService.submit(new Task4());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
        try {
            Log.d(TAG, "the result of shutdown1 is: " + future1.get());
            Log.d(TAG, "the result of shutdown1 is: " + future2.get());
            Log.d(TAG, "the result of shutdown1 is: " + future3.get());
            Log.d(TAG, "the result of shutdown1 is: " + future4.get());
        }catch (InterruptedException | ExecutionException ex){
            ex.printStackTrace();
        }catch (Exception ex){
            ex.printStackTrace();
        }

        Log.d(TAG, "----shutdown1 end---- ");
    }

    private class Task1 implements Callable{
        @Override
        public String call() throws Exception {
            int sum = 0;
            for(int i = 0; i< 10; i++){
                sum += i;
            }
            return "task1";
        }
    }

    private class Task2 implements Callable{
        @Override
        public String call() throws Exception {
            int sum = 0;
            for(int i = 0; i< 10; i++){
                sum += i;
            }
            Thread.sleep(2000);
            return "task2";
        }
    }

    private class Task3 implements Callable{
        @Override
        public String call() throws Exception {
            int sum = 0;
            for(int i = 0; i< 10; i++){
                sum += i;
            }
            Thread.sleep(2000);
            return "task3";
        }
    }

    private class Task4 implements Callable{
        @Override
        public String call() throws Exception {
            int sum = 0;
            for(int i = 0; i< 10; i++){
                sum += i;
            }
            Thread.sleep(2000);
            return "task4";
        }
    }

对应运行结果:

03-11 11:46:54.034 13116-13116/? D/simpleTest: ----shutdown1 begin---- 
03-11 11:46:54.035 13116-13116/? D/simpleTest: ----task1 begin---- 
03-11 11:46:54.035 13116-13116/? D/simpleTest: ----task2 begin---- 
03-11 11:46:54.536 13116-13116/? D/simpleTest: ----task3 begin---- 
03-11 11:46:54.537 13116-13116/? D/simpleTest: ----executorService.shutdown---- 
03-11 11:46:54.538 13116-13116/? D/simpleTest: the result of shutdown1 is: task1
03-11 11:46:56.036 13116-13116/com.example.demo_10_executorservice D/simpleTest: the result of shutdown1 is: task2
03-11 11:46:58.037 13116-13116/com.example.demo_10_executorservice D/simpleTest: the result of shutdown1 is: task3
03-11 11:46:58.038 13116-13116/com.example.demo_10_executorservice D/simpleTest: ----shutdown1 end---- 

task1用了0.5s,由于线程中间停了0.5s

task2用了2s,没问题

task3用了3.5s,1.5s等待+2s执行,有点小问题,以为它执行不了的=-=

task4由于无法执行抛出了异常,future是个空指针。

验证结果如下:

  1. 对已经执行的没影响
  2. 对正在执行的没影响
  3. 对于还在等待的任务也没影响=-=
  4. 对于shutdown后才提交的任务会抛出java.util.concurrent.RejectedExecutionException。


对于还在等待的任务有可能是有序的shutdown,正好由于时间比较短,还在等待的任务抽空执行完了?

我把task2的任务持续了20s,但task3这个任务还是抽空完成了,这里有可能有点问题,待续

(十) ExecutorService_第3张图片


3.2.2 验证shutdownNow

shutdownNow的demo只要把上面的shutdown demo改为调用shutdownNow就好了,但是结果有点奇怪。

(十) ExecutorService_第4张图片

执行到最后future2抛出了中断异常,说明shutdownNow的内部逻辑是调用到了Thread.interrupt的,而后面的future3和shutdown1 end反而调用不到了,好奇怪啊,也没有异常信息。

想了下future3的get()方法阻塞住了?shutdownNow把正在执行的任务干掉,也会让后来的任务抛出RejectedExecutionException,但是对future3这种等待中的任务没有很好地处理?

突然想起来“halts the processing of waiting tasks”指的是暂停所有等待的线程。

List list = executorService.shutdownNow();
        Log.d(TAG, "----executorService.shutdownNow---- ");

        for(Runnable runnable : list){
            Log.d(TAG, "runnable is : " + runnable.toString());
        }

然后打印出来的runnable就是一个

03-11 17:00:27.673 23862-23862/com.example.demo_10_executorservice D/simpleTest: runnable is : java.util.concurrent.FutureTask@42ffac

从这里可以看出调用shutdownNow以后暂停的任务是不能再调用get方法的,应该任务暂停了,get会一直阻塞住,会导致后续走不下去。


3.2.3 验证awaitTermination

(十) ExecutorService_第5张图片

和shutdownNow一样只改了一下调用的方法为awaitTermination,然后发现这个方法确实和api说的一样会阻塞住等所有任务都执行完,从3个任务的result打印时间可以看出是3个任务都完成了awaitTermination才结束阻塞的。

而从task4也可以得到结果来看awaitTermination和shutdown完全不是一回事,awaitTermination执行完后线程池可以继续接受任务。所以我最后加了个shutdown来关闭线程池,具体见demo,我就不贴代码了。

其他的api就略了,反正后面学习Executors还是会再来看一遍的。


4. 总结

主要了解了下ExecutorService主要api及其功能,结合具体的demo实现心中想法并得以确认,虽然还有些疑惑,留到后面学习Executors再说吧。


疑惑:shutdown调用后为什么等待的线程仍然可以执行完成?



你可能感兴趣的:(Android)