并发编程之submit和execute区别

Posted jpfss

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程之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

以上是关于并发编程之submit和execute区别的主要内容,如果未能解决你的问题,请参考以下文章

并发编程之多线程基础-Thread和Runnable的区别及联系

并发编程目录

并发编程之线程

多线程编程-之并发编程:同步容器

Java并发编程之六:Runnable和Thread实现多线程的区别(含代码)

十:并发编程之Executor线程池原理与源码解读