并发编程之submit和execute区别

前言

使用线程池难免会用到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不会出现这种情况。

原文地址:https://www.jianshu.com/p/29610984f1dd

posted @ 2019-07-15 23:12  星朝  阅读(1731)  评论(0编辑  收藏  举报