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类型参数
其实继续查看源代码发现,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不会出现这种情况。