ThreadPoolExecutor线程池submit() 和 excute()区别,顺便带上Runnable和Callable

1、 实现Runnable接口和Callable接口的区别

如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可 以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。

两者的区别一:

在于 Runnable 接口不会返回结果但 是 Callable 接口可以返回结果。

两者的区别二:

Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

备注: 工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。 ( Executors.callable(Runnable task) 或 Executors.callable(Runnable task,Object resule) )。

这里看Runnable和Callable的源代码就知道为什么Runnable 接口不会返回结果但 是 Callable 接口可以返回结果。实现 Runnable 接口和Callable 接口的主要目的是为了让线程执行call()或者run()里面的代码,而call()是有返回值的,run()没有返回值。

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}





@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

2、 ThreadPoolExecutor中的execute()方法和submit()方法的区别是什么呢?

(1)接收的参数不一样

 execute()方法只接收Runnable类型的参数,而submit()方法既接收Runnable类型也接收Callable类型参数

ThreadPoolExecutor线程池submit() 和 excute()区别,顺便带上Runnable和Callable

ThreadPoolExecutor线程池submit() 和 excute()区别,顺便带上Runnable和Callable

其实继续查看源代码发现,execute()方法最后干事的是Runnable里面的run()方法,而submit()方法里面最后干事的是Callable里面的call()方法。解析如下:不嫌麻烦的话可以一看:

查看AbstractExecutorService源码,发现submit方法是在此抽象父类中实现:

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

/**
 * @throws RejectedExecutionException {@inheritDoc}
 * @throws NullPointerException       {@inheritDoc}
 */
public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}

/**
 * @throws RejectedExecutionException {@inheritDoc}
 * @throws NullPointerException       {@inheritDoc}
 */
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

不管你submit的时候传入的是Runnable还是Callable,最后RunnableFuture(FutureTask)里面都会生成Callable对象。(可以去看源码就知道了)

可以看到,submit方法是先构造出一个RunnableFuture(FutureTask) 然后调用execute方法。

execute(ftask)执行时调用的是RunnableFuture(FutureTask)里的run方法(RunnableFuture继承自Runnable),而rRunnableFuture(FutureTask)里的run方法又调用Callable对象的call方法。

---------------------------------------------------------------------------------------


而如果直接调用execute(Runnale runnable)方法的话,则会直接调用Runnable中的run()方法,不会先构造RunnableFuture(FutureTask)。

(2)返回值不一样

 execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;

submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断 任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit) 方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行 完。

(3)异常处理不一样

execute在运行时,如果你在你的task里会抛出checked或者unchecked exception时,execute()方法是会直接抛出异常的,而submit()方法就不会抛出异常,而是另作处理。

从上面的源代码就看出:

发现submit底层调用的还是execute,但是提交的任务不是task,而是在task的基础上封装了一层FutureTask:

RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(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不会出现这种情况。

 

 

上一篇:spark内核源码深度剖析(2)--Spark的三种提交模式


下一篇:HTML标签天天练7--表单3[action][submit][reset]