14-线程池源码Executor

线程池源码Executor

一、Executor

  • Executor:执行任务顶层接口,只定义了一个方法,可以执行Runnable对象
public interface Executor {
    /**
     * 执行任务接口 
     */
    void execute(Runnable command);
}

二、ExecutorService

  • ExecutorService是Executor的子接口,它对Executor做了一定的扩展,除了可以执行Runnable之外,还能支持执行Callable返回Future,另外还包含一些调用和状态管理接口,比如shutdownNow,shutdown。
public interface ExecutorService extends Executor {

    /**
     *关闭和状态判断相关方法
    */
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();

    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
    
    /**
     *submit提交Callable任务
    */
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);

    //执行全部任务,返回一个Future类型的List,包含所有的任务状态     
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
    <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;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

三、AbstractExecutorService

  • AbstractExecutorService是ExecutorService接口的抽象实现类,实现了ExecutorService的大部分方法,主要是invokeAll和submit,submit会返回Future(实际返回的是FutureTask),下面主要看一下实现的submit和invokeAll方法。

3.1 submit

public Future<?> submit(Runnable task) {
    if (task == null) 
        throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

//将Runnable封装为一个FutureTask
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
}
  • 注意这里的execute方法是顶层接口Executor定义的,需要子类实现,也就是说具体的执行逻辑还没有实现,只是把一些能够固定的逻辑和返回值给确定下来。

3.2 invokeAll

  • invokeAll是执行一个任务集合里面的全部任务。分析后发现invokeAll触发执行任务列表,返回的结果顺序也与任务在任务列表中的顺序一致(由有序的数组来表示结果),且会阻塞等待所有线程执行完任务后才返回结果,如果设置了超时时间,未超时完成则正常返回结果,如果超时未完成则会取消全部任务。
  • 注意源码里面借助了FutureTask,它将任务封装为FutureTask对象,既可以执行,也可以作为结果返回,因此FutureTask同时实现了Runnable和Future接口。
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        //1.定义需要返回的Future数组,数组每个元素对应每一个任务的Future对象
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            for (Callable<T> t : tasks) {
                //2.将任务封装成一个FutureTask
                RunnableFuture<T> f = newTaskFor(t);
                //3.将返回的RunnableFuture加到数组
                futures.add(f);
                //4.执行任务,该逻辑由子类实现
                execute(f);
            }
            //5.等到每个任务都结束
            for (int i = 0, size = futures.size(); i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    try {
                        //6.阻塞获取结果,如果有超时参数,将超时参数传给future.get即可         
                        f.get();
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    }
                }
            }
            //7.返回
            done = true;
            return futures;
        } finally {
            //8.如果f.get()有任何一个出现异常,则无法完成,这里就将全部任务取消;
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }
    
//支持超时模式
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        //1.定义需要返回的Future数组,数组每个元素对应每一个任务的Future对象
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            //2.包装结果
            for (Callable<T> t : tasks)
                futures.add(newTaskFor(t));
            
            final long deadline = System.nanoTime() + nanos;
            final int size = futures.size();

            // Interleave time checks and calls to execute in case
            // executor doesn't have any/much parallelism.
            //3.执行任务,如果超时时间小于0,就不超时,阻塞直到获取结果
            //这里有一个注意的地方就是,futures数组即代表任务,也代表结果,也就是它既可以作为任务执行,也可以代表结果返回
            //因为通过newTaskFor包装成了FutureTask对象,FutureTask同时实现了Runnable和Future接口
            for (int i = 0; i < size; i++) {
                execute((Runnable)futures.get(i));//FutureTask可以执行
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L)
                    return futures;//FutureTask也可以作为结果返回
            }
            //4.如果执行完没有超时,就阻塞等待获取结果,不过需要考虑超时
            for (int i = 0; i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    if (nanos <= 0L)
                    //5.超时了就直接返回
                        return futures;
                    try {
                        f.get(nanos, TimeUnit.NANOSECONDS);
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    } catch (TimeoutException toe) {
                        return futures;
                    }
                    //6.重新计算超时时间
                    nanos = deadline - System.nanoTime();
                }
            }
            done = true;
            //7.返回结果
            return futures;
        } finally {
            //8.如果f.get()有任何一个出现异常,则无法完成,这里就将全部任务取消;
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }

3.3 invokeAny

  • invokeAny获取一个最快执行完毕的任务,必将任务结果返回,内部实现机制上,通过CompletionService来获取最先完成任务的结果。在循环中每一次会判断是否已经有某一个任务完成了,如果有就返回结果,如果没有,就会执行下一个任务,如果全部任务都在执行并且都还没执行好,就会等待最快的结果,如果有超时参数并且超时了,就会抛出异常
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException, TimeoutException {
        //1.参数校验
        if (tasks == null)
            throw new NullPointerException();
        int ntasks = tasks.size();
        if (ntasks == 0)
            throw new IllegalArgumentException();
        
        //2.保存结果,这里用到了CompletionService,CompletionService可以让我们按照任务的完成顺序,优先
        //获取到已经完成的任务的结果,具体可阅读参考文章
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
        ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);

        // For efficiency, especially in executors with limited
        // parallelism, check to see if previously submitted tasks are
        // done before submitting more of them. This interleaving
        // plus the exception mechanics account for messiness of main
        // loop.

        try {
            // Record exceptions so that if we fail to obtain any result, we can throw the last exception we got.
            //3.记录异常和超时时间,如果获取结果异常就把最后的一个异常抛出
            ExecutionException ee = null;
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Iterator<? extends Callable<T>> it = tasks.iterator();

            // Start one task for sure; the rest incrementally
            //4.启动一个任务,任务数减一,活跃数置为1
            futures.add(ecs.submit(it.next()));
            --ntasks;
            int active = 1;

            //5.循环中处理,直到返回或者break
            for (;;) {
                //6.尝试获取第一个任务的结果(如果结果队列是空的就会返回null)
                Future<T> f = ecs.poll();
                if (f == null) {
                    //7.如果没有获取到,说明前一个任务还未完成,并且判断还有任务,那么就再执行下一个任务
                    if (ntasks > 0) {
                        --ntasks;
                        futures.add(ecs.submit(it.next()));
                        ++active;
                    }
                    //8.到这说明没有活跃任务,说明通过循环所有的任务都在执行中了,就break
                    else if (active == 0)
                        break;
                    //9.到这里说明任务结果也没拿到,也没有剩余任务了,那就只能等正在执行的任务结果了,并且是超时模式,需要考虑超时时间,超时了就抛出异常
                    else if (timed) {
                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                        if (f == null)
                            throw new TimeoutException();
                        nanos = deadline - System.nanoTime();
                    }
                    //10.这里和前一个逻辑一样,但是不需要考虑超时,阻塞等待结果take是阻塞的,为空就会等待,前面的poll不会阻塞,这里会一直
                    //等到一个结果,然后下一次循环就可以在下面的if逻辑中返回了
                    else
                        f = ecs.take();
                }
                if (f != null) {
                    //11.如果获取到了,就返回这个结果
                    --active;
                    try {
                        return f.get();
                    } catch (ExecutionException eex) {
                        ee = eex;
                    } catch (RuntimeException rex) {
                        ee = new ExecutionException(rex);
                    }
                }
            }
            //12.出现了异常就抛出异常
            if (ee == null)
                ee = new ExecutionException();
            throw ee;

        } finally {
            //13.取消其余任务
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
        }
    }
  • 小结后发现,在AbstractExecutorService里面实现了方法的主体,比如异常判断,结果的包装和返回,但是核心的execute还行需要子类实现,这也是模板方法的体现。

四、示例

4.1 invokeAll

4.1.1 非超时模式
  • 在非超时模式下,invokeAll会阻塞然后返回全部的结果,如下所示每个任务会随机休眠5S,然后invokeAll会等到全部任务执行完毕。
public class InvokeAllTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<String>> list = new ArrayList<>();
        for (int i = 1; i < 10; i++) {
            list.add(new MyTask(i+""));
        }
        List<Future<String>> futures = null;
        try {
            futures = executorService.invokeAll(list);
        } catch (InterruptedException e) {
            System.err.println("InterruptedException ...");
            e.printStackTrace();
        }

        for (Future f : futures) {
                System.out.println(f.get() + " -- " + System.currentTimeMillis());
        }
        executorService.shutdown();
    }

    static class MyTask implements Callable<String> {

        String id;
        public MyTask(String id) {
            this.id = id;
        }

        @Override
        public String call() throws Exception {
            long begin = System.currentTimeMillis();
            SleepTools.randomMs(5000);
            return id +" -- "+ (System.currentTimeMillis() - begin);
        }
    }
}

打印:
1 -- 4682 -- 1571455109326
2 -- 1880 -- 1571455109326
3 -- 1859 -- 1571455109326
4 -- 1311 -- 1571455109326
5 -- 108 -- 1571455109326
6 -- 4077 -- 1571455109326
7 -- 625 -- 1571455109326
8 -- 1770 -- 1571455109326
9 -- 4081 -- 1571455109326

  • 可以看到不同线程休眠时间不同,但是所有的结果是一次打包返回的
4.1.2 超时模式
  • 下面我设置了超时时间是3S,因此无法等到全部任务执行完毕,但是根据invokeAll的超时模式源码分析,3S内已经执行完毕的任务会打包返回,然后会取消任务,取消的底层是通过线程的interrupt尝试中断来实现的,然后主线程通过future来get结果的时候,如果线程已经被取消,则会抛出CancellationException
public class InvokeAllTimedTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<String>> list = new ArrayList<>();
        for (int i = 1; i < 10; i++) {
            list.add(new MyTask(i+""));
        }
        List<Future<String>> futures = null;
        try {
            futures = executorService.invokeAll(list, 3, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            System.err.println("InterruptedException ...");
            e.printStackTrace();
        }

        for (Future f : futures) {
            try {
                System.out.println(f.get() + " -- " + System.currentTimeMillis());
            } catch (InterruptedException e) {
                System.err.println("InterruptedException ...");
                e.printStackTrace();
            } catch (ExecutionException e) {
                System.err.println("ExecutionException ...");
                e.printStackTrace();
            }catch (Exception ex){
                System.out.println(ex.getClass());
            }
        }
        executorService.shutdown();
    }
    //省略...
}

打印:
1 -- 2256 -- 1571455160560
class java.util.concurrent.CancellationException
3 -- 1906 -- 1571455160560
4 -- 481 -- 1571455160560
5 -- 2311 -- 1571455160560
6 -- 639 -- 1571455160560
class java.util.concurrent.CancellationException
8 -- 1149 -- 1571455160560
9 -- 2366 -- 1571455160560
  • 可以看到增加了超时参数之后,只会返回在超时参数内已经返回结果的任务,其余的任务会被取消,部分任务会抛出CancellationException

4.2 invokeAny

4.2.1 非超时模式
  • invokeAny只会返回第一个结果
public class InvokeAnyTest {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<String>> list = new ArrayList<>();
        for (int i = 1; i < 10; i++) {
            list.add(new MyTask(i+""));
        }
        String result = executorService.invokeAny(list);
        System.out.println(result);
        executorService.shutdown();
    }

    static class MyTask implements Callable<String> {

        String id;
        public MyTask(String id) {
            this.id = id;
        }

        @Override
        public String call() throws Exception {
            long begin = System.currentTimeMillis();
            SleepTools.randomMs(10000);
            return id +" -- "+ (System.currentTimeMillis() - begin);
        }
    }
}

打印:
8 -- 159
  • 注意这里每次打印的结果都会不一样,会返回最快执行完毕的任务结果,CompletionService原理其实不难,他就是包装了一下FutureTask,并且重写了一个任务执行完毕后的钩子方法,完毕后将自己的结果放在阻塞队列里面,由此通过阻塞队列就总能按照任务完成的速度,优先拿到先完成的任务结果。
4.2.2 超时模式
  • 超时模式下,如果超时时间很短,尚未有任何一个任务完成,就会抛出TimeoutException异常,前面的源码中有分析
  • 如下代码我只是将超时时间设置的比较小,任务随机休眠5S以内,设置100ms大部分时候会超时,打印就是null
public class InvokeAnyTimedTest {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<String>> list = new ArrayList<>();
        for (int i = 1; i < 10; i++) {
            list.add(new MyTask(i + ""));
        }
        String result = null;
        try {
            result = executorService.invokeAny(list, 100, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            System.err.println(e.getClass());
            e.printStackTrace();
        }
        System.out.println(result);
        executorService.shutdown();
    }

    //省略...
}

//异常打印,超时时间设置为100ms
class java.util.concurrent.TimeoutException
java.util.concurrent.TimeoutException
	at java.util.concurrent.AbstractExecutorService.doInvokeAny(AbstractExecutorService.java:184)
	at java.util.concurrent.AbstractExecutorService.invokeAny(AbstractExecutorService.java:225)
	at com.intellif.mozping.threadpool.invokeany.InvokeAnyTimedTest.main(InvokeAnyTimedTest.java:25)
null
  • 这里异常栈(AbstractExecutorService.java:184),对应的就是前面分析的抛出异常的源码位置
  • 如果我把超时时间设置为300ms,有时候会成功,有时候也抛异常,因为任务的休眠时间是随机的

五、小结

  • 文章主要分析了Executor接口,ExecutorService接口和AbstractExecutorService抽象类的继承关系和主要方法
  • 重点看了AbstractExecutorService的主要方法源码invokeAll和doInvokeAny,他们的行为如下
方法模式行为描述
invokeAll不超时阻塞等所有任务执行完毕,将全部结果打包返回
invokeAll超时超时后会把全部Future打包返回,完成的任务可以获取结果,未完成的任务被取消,通过future.get()获取结果的时候会抛出CancellationException
doInvokeAny不超时阻塞,返回最快执行完毕的任务结果
doInvokeAny超时超时时间内有任务执行完毕则返回最快的任务结果,没有则抛出TimeoutException,
  • 源码用到了CompletionService和FutureTask,需要对着两个类有一定了解

六、参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值