七、ExecutorService的API

7.1 API overview

首先来概览一下,这个接口大概有哪些重要API:
在这里插入图片描述

  • invokeAny()invokeAll() 具有阻塞特性
  • invokeAny() 取得第一个完成任务的结果值,当第一个任务执行完成后,调用interrupt()中断其他任务,所以可以结合if(Thread.intterruptted())决定任务是否继续运行。
  • invokeAll()全部线程任务执行完成后,取得全部完成任务的结果值。

7.2 invokeAny

invokeAnyinvokeAll 都具有阻塞特性

1、invokeAny(Collection tasks) 与interrupted状态

  • tasks执行完任意一个,才会唤醒主线程。并且会返回 第一个执行完任务的结果。
  • tasks中假设有 t1 ,t2 两个任务,t1快 t2慢,则t1完成时,会给t2 状态设置为interrupted。可在t2 中增加if(Thread.interrputed)的判断,中断 正在执行的t2线程。(见demo7.2.1)
// demo7.2.1

public class MyExecutorService {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<Callable<String>> list = new ArrayList<>();
        list.add(new MyCallableA());
        list.add(new MyCallableB1());

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // invokeAny --> 只取最先完成任务的结果值
        // 从结果来看就是:打印出 any 以后,主线程继续运行,而 MyCallableB1 线程继续执行完
        // invokeAny --> 此方法具有阻塞特性,主线程会在此阻塞
        String any = executorService.invokeAny(list);
        System.out.println("invokeAny method gets value " + any);
        System.out.println("main thread ends here!");
    }
}

class MyExecutorService2{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<Callable<String>> list = new ArrayList<>();
        list.add(new MyCallableA());
        list.add(new MyCallableB2());

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // invokeAny --> 只取最先完成任务的结果值
        // 从结果来看就是:打印出 any 以后,主线程继续运行,而 MyCallableB2  线程继续执行完
        //而由于 MyCallableB2 中会对 当前线程状态做出判断,假如是已标记为“ interrupted” ,就
        // 抛出异常中断,需要注意的是: MyCallableB2 中断后,异常并不会传递到 主线程
        // invokeAny --> 此方法具有阻塞特性,主线程会在此阻塞
        String any = executorService.invokeAny(list);
        System.out.println("invokeAny method gets value " + any);
        System.out.println("main thread ends here!");
    }
}

class MyCallableA implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("MyCallableA begins " + System.currentTimeMillis());
        IntStream.range(0, 10_00).forEach(e -> {
            Math.random();Math.random();Math.random();
            System.out.println("MyCallableA " + (e + 1));
        });
        System.out.println("MyCallableA ends " + System.currentTimeMillis());
        return " return A ";
    }
}

class MyCallableB1 implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("MyCallableB1 begins " + System.currentTimeMillis());
        IntStream.range(0, 20_00).forEach(e -> {
            Math.random();Math.random();Math.random();
            System.out.println("MyCallableB1 " + (e + 1));
        });
        System.out.println("MyCallableB1 ends " + System.currentTimeMillis());
        return "return B1 ";
    }
}

class MyCallableB2 implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("MyCallableB2 begins " + System.currentTimeMillis());
        for (int e = 0; e < 300_000; e++) {
            if (!Thread.currentThread().isInterrupted()) {
                Math.random();Math.random();Math.random();
                System.out.println("MyCallableB2 " + (e + 1));
            } else {
                System.out.println(" 抛异常了,中断了!");
                throw new InterruptedException("中断了");
            }
        }
        System.out.println("MyCallableB2 ends " + System.currentTimeMillis());
        return "return B2";
    }
}

2、invokeAny(Collection tasks) 与 执行慢的任务异常

假设 tasks 里有t1 、 t2两个任务:

  • a. 设 t1 快,t2 慢,t1正常,t2异常。默认不会在主线程输出异常,要显式在t2中抓异常。即使在t2 中显式地抓住了异常,该异常仍然不会影响到主线程的正常执行,言下之意是,主线程捕捉不到子线程抛出的异常(demo 7.2.2 & demo 7.2.3)

  • b. 设t1 快,t2 慢,t1 异常,t2 正常,则t1 的异常仍然不能显示到控制台,而需要显式try catch,同时会等待慢的任务t2返回结果值。 假如t1 中显式 try catch 了后没有重新将这个异常抛出,则仍将返回 t1 的结果,因为在主线程看来,并没有收到 t1 异常的消息(这个异常被吞掉了)。
    注:先出现异常,但不影响后面任务取值的原理在于源码中一直判断(死循环)有无正确的返回值,若到最后都无返回值则抛出异常,这个异常就是最后出现的异常。参见: AbstractExcecutorService method: doInvokeAny()
    //todo 源码分析

  • c. 设tasks 里有三个任务A、B、C,设A B C 一起执行,都有异常,最终的异常就是最后一个出现的异常。(demo 7.2.4)

总结一下:tasks 任务中假如有正常的,则返回最后一个正常任务的结果;
假如都是异常的,则主线程也会抛出异常。
假如任务中try catch中显式地 捕获了异常,却没有再次往上抛,则该任务对主线程来说是正常的(相当于异常被“吞掉”了)。

// demo 7.2.2
class MyExecutorService3{
    public static void main(String[] args) {
        Callable<String> callable1 = () -> {
            System.out.println("callable1 " + Thread.currentThread().getName() +
                    " begins " + System.currentTimeMillis());
            IntStream.range(0, 1_000).forEach(e -> {
                Math.random();Math.random();Math.random();
                System.out.println(" callable1 running " + (e + 1));
            });
            System.out.println("callable1 " + Thread.currentThread().getName() +
                    " ends " + System.currentTimeMillis());
            return " callable1";
        };

        Callable<String> callable2 = () -> {
            System.out.println("callable2 " + Thread.currentThread().getName() +
                    " begins " + System.currentTimeMillis());
            IntStream.range(0, 1_0000).forEach(e -> {
                Math.random();Math.random();Math.random();Math.random();
                System.out.println(" callable2 running " + (e + 1));
            });
            if (true) {
                // 此处抛出的NPE不会在主线程打印出来。必须显式地 try catch,才能捕捉到异常,打印到控制台
                System.out.println("===开始中断 callable2 ====");
                throw new NullPointerException("被抛出的NPE");
            }
            System.out.println("callable2 " + Thread.currentThread().getName() +
                    " ends " + System.currentTimeMillis());
            return " callable2 ";
        };

        ArrayList<Callable<String>> callables = Lists.newArrayList(callable1, callable2);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {
            String any = executorService.invokeAny(callables);
            System.out.println(" 获取 执行结果值: " + any);
            System.out.println(" 主线程继续执行 ");
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println(" main 1 ");
        } catch (ExecutionException e) {
            e.printStackTrace();
            System.out.println(" main 2 ");
        }

    }
}

// demo 7.2.3
class MyExecutorService4{
    public static void main(String[] args) {
        Callable<String> callable1 = () -> {
            System.out.println("callable1 " + Thread.currentThread().getName() +
                    " begins " + System.currentTimeMillis());
            IntStream.range(0, 1_000).forEach(e -> {
                Math.random();Math.random();Math.random();
                System.out.println(" callable1 running " + (e + 1));
            });
            System.out.println("callable1 " + Thread.currentThread().getName() +
                    " ends " + System.currentTimeMillis());
            return " callable1";
        };

        // 显式 try catch ,将错误捕捉到
        Callable<String> callable2 = () -> {
            try {
                System.out.println("callable2 " + Thread.currentThread().getName() +
                        " begins " + System.currentTimeMillis());
                IntStream.range(0, 1_0000).forEach(e -> {
                    Math.random();Math.random();Math.random();Math.random();
                    System.out.println(" callable2 running " + (e + 1));
                });
                if (true) {
                    // 此处抛出的NPE不会在主线程打印出来。必须显式地 try catch,才能捕捉到异常,打印到控制台
                    System.out.println("===开始中断 callable2 ====");
                    throw new NullPointerException("被抛出的NPE");
                }
                System.out.println("callable2 " + Thread.currentThread().getName() +
                        " ends " + System.currentTimeMillis());
            } catch (NullPointerException e) {
                e.printStackTrace();
                // =======捕捉到 异常之后,再主动抛出去=====
                throw e;
            }
            return " callable2 ";
        };

        ArrayList<Callable<String>> callables = Lists.newArrayList(callable1, callable2);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {
            String any = executorService.invokeAny(callables);
            System.out.println(" 获取 执行结果值: " + any);
            System.out.println(" 主线程继续执行 ");
            
            // ====主线程里无法 catch 到 子线程抛出的异常(线程间不直接通信)
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println(" main 1 ");
        } catch (ExecutionException e) {
            e.printStackTrace();
            System.out.println(" main 2 ");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(" main 3 ");
        }

    }
}

//demo :7.2.4
class MyExecutorService5 {
    public static void main(String[] args) {
        
        // 快
        Callable<String> callable1 = () -> {
            System.out.println("callable1 " + Thread.currentThread().getName() +
                    " begins " + System.currentTimeMillis());
            IntStream.range(0, 1_000).forEach(e -> {
                Math.random();Math.random();Math.random();
                System.out.println(" callable1 running " + (e + 1));
            });
            if (true) {
                System.out.println(" ==== 开始中断 callable1 === ");
                throw new RuntimeException("被抛出的NPE callable1 ");
            }
            System.out.println("callable1 " + Thread.currentThread().getName() +
                    " ends " + System.currentTimeMillis());
            return " callable1";
        };

        // 慢
        Callable<String> callable2 = () -> {
            System.out.println("callable2 " + Thread.currentThread().getName() +
                    " begins " + System.currentTimeMillis());
            IntStream.range(0, 1_0000).forEach(e -> {
                Math.random();Math.random();Math.random();Math.random();
                System.out.println(" callable2 running " + (e + 1));
            });
            if (true) {
                // 此处抛出的NPE不会在主线程打印出来。必须显式地 try catch,才能捕捉到异常,打印到控制台
                System.out.println("===开始中断 callable2 ====");
                throw new NullPointerException("被抛出的NPE callable2 ");
            }
            System.out.println("callable2 " + Thread.currentThread().getName() +
                    " ends " + System.currentTimeMillis());
            return " callable2 ";
        };

        ArrayList<Callable<String>> callables = Lists.newArrayList(callable1, callable2);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {
            // 由于两个 子任务都抛出了异常,主线程实际上也会抛异常
            String any = executorService.invokeAny(callables);
            System.out.println(" 获取 执行结果值: " + any);
            System.out.println(" 主线程继续执行 ");

        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println(" main 1 ");
        } catch (ExecutionException e) {
            e.printStackTrace();
            // 进入了这个方法块!主线程 捕捉到了 子线程的异常!
            System.out.println(" main 2 ");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(" main 3 ");
        }

    }
}

3、invokeAny(Collection tasks, timeout ,timeUnits)

在指定的时间内取得第一个先执行完任务的结果值。假如在指定时间内没能执行完任务,将会抛出异常。超时时,执行中的任务状态会变成“interrupted”,所以可以结合

if(Thread.current().isInterrupted) {
	throw new InterruptionExceptioin()
}

来使运行中的线程通过抛异常中断。

再提出一个具体的场景:假如一个任务在执行时既出现了异常,又超时了,会出现什么现象呢?

答曰:主线程超时后会抛出异常,中断主线程的继续执行,所以不能获取任务的执行结果;主线程抛出异常后,子任务会在执行完毕后被interrupt。不过需要注意的是,如果希望记录子线程的异常,需要主动try catch ,若只是简单throw 出去,是打印不了异常栈轨的。//demo 7.2.5

// ===================下面都是代码demo====================

// demo 7.2.5
class MyExecutorService6{
    public static void main(String[] args) {
        Callable<String> callable1 = () -> {
            System.out.println("callable1 " + Thread.currentThread().getName() +
                    " begins " + System.currentTimeMillis());
            IntStream.range(0, 1_234).forEach(e -> {
                Math.random();Math.random();Math.random();Math.random();Math.random();
                System.out.println(" callable1 running " + (e + 1));
            });
            if (true) {
                System.out.println(" ==== 开始中断 callable1 === ");
                // 注: 假如此处不使用 try catch 抓住异常,那么抛出的异常是不会在控制台显示的
                throw new NullPointerException("被抛出的NPE callable1 ");
            }
            System.out.println("callable1 " + Thread.currentThread().getName() +
                    " ends " + System.currentTimeMillis());
            return " callable1";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {
            //
            String any = executorService.invokeAny(Lists.newArrayList(callable1), 10 , TimeUnit.MILLISECONDS);
            System.out.println("获取 任务结果" + any);
            System.out.println("---------");
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println(" main A ");
        } catch (ExecutionException e) {
            e.printStackTrace();
            System.out.println(" main B ");
        } catch (TimeoutException e) {
            // ======主线程会进入本代码块,表示 主线程已经抛出了异常,try 中的任务结果不能再获取了=====
            e.printStackTrace();
            System.out.println("main C");
        }

    }
}

7.3 invokeAll

7.3.1 List<Future< T>> invokeAll(Collection tasks)

这是最简单的情形,调用方将会阻塞在invokeAll这一行,直到所有的tasks都执行完,或者抛出异常,见 demo 7.3.1
方法API:
invokeAll

// demo 7.3.1 
class MyExecutorService7{
    public static void main(String[] args) {
        Callable<String> callableA = () -> {
            System.out.println(Thread.currentThread().getName() + " begin " + DateUtil.currentTime());
            TimeUnit.SECONDS.sleep(5);
            System.out.println(Thread.currentThread().getName() + " end " + DateUtil.currentTime());
            return " return A";
        };

        Callable<String> callableB = () -> {
            System.out.println(Thread.currentThread().getName() + " begin " + DateUtil.currentTime());
            TimeUnit.SECONDS.sleep(8);
            System.out.println(Thread.currentThread().getName() + " end " + DateUtil.currentTime());
            return " return B";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {
            // invokeAll 会阻塞,直到tasks中的最后一个任务执行完,或者抛出异常
            System.out.println(" 开始执行时间 "+ DateUtil.currentTime());
            List<Future<String>> futures = executorService.invokeAll(Lists.newArrayList(callableA, callableB));
            System.out.println(" 执行完时间 "+ DateUtil.currentTime());
            for (Future<String> future : futures) {
                // 实际在get() 的时候就可能抛出 ExecutionException
                // get() 会阻塞
                String getStr = future.get();
                System.out.println(" 返回结果 " + getStr);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println(" main A ");
        } catch (ExecutionException e) {
            e.printStackTrace();
            System.out.println(" main B");
        }
    }
}


7.3.2 List<Future< T> invokeAll(Collection tasks)中tasks的异常处理

假如不从invokeAll()中获取结果,即不执行future.get()方法,那么结果tasks中抛出异常也不影响主线程;
假如需要从invokeAll() 中获取结果,则tasks中任务的顺序可能影响主线程的执行流程。
请看demo 7.3.2。
小伙伴们应该能看明白:
异常也是一种任务执行结果,跟正常的执行结果一样,假如主线程不主动从Future中获取执行结果,那么任务的异常是不会传递到主线程中的。

// demo 7.3.2
class MyExecutorService9 {
    public static void main(String[] args) {
        Callable<String> callableA = () -> {
            System.out.println(" callable A begins" + DateUtil.currentTime());
            // 此处抛出异常
            int i = 1 / 0;
            System.out.println("' callable A ends " + DateUtil.currentTime());
            return " callable A";
        };

        Callable<String> callableB = () -> {
            System.out.println(" callable B begins" + DateUtil.currentTime());
            IntStream.range(0, 100).forEach(u -> {
                Math.random();
                Math.random();
                Math.random();
            });
            System.out.println("' callable B ends " + DateUtil.currentTime());
            return " callable B";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {
        // 任务顺序影响 后面for()循环中获取执行结果
//            List<Future<String>> futures = executorService.invokeAll(Lists.newArrayList(callableA, callableB));
            List<Future<String>> futures = executorService.invokeAll(Lists.newArrayList(callableB, callableA));
            for (Future<String> future : futures) {
                // 注意: 实际的异常是从 get() 抛出来的
                System.out.println(future.get());
            }
        } catch (InterruptedException e) {
            System.out.println(" caught exception in main 1 ");
            e.printStackTrace();
        } catch (ExecutionException e) {
            System.out.println(" caught exception in main 2 ");
        } finally {
            executorService.shutdown();
        }


    }
}

7.3.3 List< Future < T>>invokeAll(Collection tasks, timeout, timeUnit)

该API作用,如其传参所示,就是在指定时间内获取任务执行结果的。假如在指定时间里未能完成所有任务,调用Future.get()方法 将会抛出CancellatioinException异常。注意这点和invokeAny()抛出来的TimeoutException倒是不同。
其实我们直觉上去理解:

  • invokeAll()的返回值其实是封装了多个Future(执行结果:包括正常结果+异常),假如在执行时间内没有完成所有任务,那么未完成的任务实际上相当于被“取消(cancel)”了,所以通过Future.get()会抛出 CancellationException`;
  • invokeAny()只会返回一个结果,若在指定时间内没有返回,说明所有的任务都没完成,所以会抛出TimeoutException

7.4 总结

ExecutorService定义了 管理Future异步任务的接口,我们常说的“线程池对象”其实就是ExecutorService的基本实现。ExecutorService定义的invokeAny \ invokeAll方法规定了如何获取一组任务 的结果。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐