美文网首页三个JAVA臭皮匠
并发编程之submit和execute区别(七)

并发编程之submit和execute区别(七)

作者: 后厂村老司机 | 来源:发表于2018-08-29 10:38 被阅读6次

    前言

    使用线程池难免会用到submit和execute,但是submit是有坑的,此处做个记录

    1、submit坑

    此处随便写一个方法,进入内部查看execute和submit

    /**
     * @Author: 小混蛋
     * @CreateDate: 2018/8/29 9:58
     */
    @Component
    public class Test {
        public static void main(String[] args) {
            ExecutorService es = Executors.newFixedThreadPool(5);
            ArrayList<Future<?>> arrayList = new ArrayList();
            for (int i = 0; i < 10; i++) {
                final int b = i;
                Future<?> submit = es.submit(() -> {
                    System.out.println(Thread.currentThread().getName());
                    int a = b / 0;
                });
                arrayList.add(submit);
            }
            arrayList.forEach(s -> {
                try {
                    s.get();
                } catch (InterruptedException |ExecutionException e) {
                    e.printStackTrace();
                }
            });
            es.shutdown();
        }
        @Scheduled(cron = "")
        public void test() {
    
        }
    }
    

    ctrl加鼠标左键进入submit,查看AbstractExecutorService,发现submit底层调用的还是execute,但是提交的任务不是task,而是在task的基础上封装了一层FutureTask

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

    重点来了,当submit提交的task里面出现未检查异常如RuntimeException和Error等,直接execute你的task肯定是抛异常;但是使用submit之后提交的FutureTask我们看下它的源码run方法:run方法和我们直接提交的task的run方法并不一样,该方法会对所有的Throwable类型进行捕获,并把异常通过setException保存在内部变量outcome里面。所以线程池执行的过程中异常不会被抛出

    public void run() {
            if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                             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 {
                runner = null;
                int s = state;
                if (s >= INTERRUPTING)
                    handlePossibleCancellationInterrupt(s);
            }
        }
    protected void setException(Throwable t) {
            if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
                outcome = t;
                UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
                finishCompletion();
            }
        }
    

    另一个重点来了,当submit被futuretask.get的时候。会在report方法调用过程中抛出这个未检查异常!

    public V get() throws InterruptedException, ExecutionException {
            int s = state;
            if (s <= COMPLETING)
                s = awaitDone(false, 0L);
            return report(s);
        }
    private V report(int s) throws ExecutionException {
            Object x = outcome;
            if (s == NORMAL)
                return (V)x;
            if (s >= CANCELLED)
                throw new CancellationException();
            throw new ExecutionException((Throwable)x);
        }
    

    结论

    1、submit在执行过程中与execute不一样,不会抛出异常而是把异常保存在成员变量中,在FutureTask.get阻塞获取的时候再把异常抛出来。
    2、Spring的@Schedule注解的内部实现就是使用submit,因此,如果你构建的任务内部有未检查异常,你是永远也拿不到这个异常的。
    3、execute直接抛出异常之后线程就死掉了,submit保存异常线程没有死掉,因此execute的线程池可能会出现没有意义的情况,因为线程没有得到重用。而submit不会出现这种情况。

    相关文章

      网友评论

        本文标题:并发编程之submit和execute区别(七)

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