线程池execute 和 submit 的区别

打印 上一主题 下一主题

主题 650|帖子 650|积分 1950

1. execute 和 submit 的区别

前面说了还需要介绍多线程中使用 execute 和 submit 的区别(这两个方法都是线程池 ThreadPoolExecutor 的方法)。
1.1 方法来源不同

execute 方法是线程池的顶层接口 Executor 定义的,在 ThreadPoolExecutor 中实现:
  1. void execute(Runnable command);
复制代码
submit()是在ExecutorService接口中定义的,并定义了三种重载方式:
  1. <T> Future<T> submit(Callable<T> task);
  2. <T> Future<T> submit(Runnable task, T result);
  3. Future<?> submit(Runnable task);
复制代码
在AbstractExecutorService类中有它们的具体实现,而ThreadPoolExecutor继承了AbstractExecutorService类。所以 ThreadPoolExecutor 也有这三个方法。
1.2 接收参数不同

从上面的方法来源中可以看出,二者接收参数类型不同:

  • execute()方法只能接收实现Runnable接口类型的任务
  • submit()方法则既可以接收Runnable类型的任务,也可以接收Callable类型的任务
1.3 返回值不同

由于 Runnable 和 Callable 的区别就是,Runnable 无返回值,Callable 有返回值。
所以 execute 和 submit  的返回值也不同。

  • execute()的返回值是void,线程提交后不能得到线程的返回值
  • submit()的返回值是Future,通过Future的get()方法可以获取到线程执行的返回值,get()方法是同步的,执行get()方法时,如果线程还没执行完,会同步等待,直到线程执行完成
    1. 虽然submit()方法可以提交Runnable类型的参数,但执行Future方法的get()时,线程执行完会返回null,不会有实际的返回值,这是因为Runable本来就没有返回值
    复制代码
1.4 异常处理机制不同


  • 用 submit 提交任务,任务内有异常也不会打印异常信息,而是调用get()方法时,打印出任务执行异常信息
  • 用 execute 提交任务时,任务内有异常会直接打印出来
后面源码分析中会体现这个不同点!
2. execute 和 submit 源码分析

2.1 submit 源码分析

submit 方法是 ExecutorService 接口定义,由 AbstractExecutorService 抽象类实现,有三个重载方法:
  1. public Future<?> submit(Runnable task) {
  2.     if (task == null) throw new NullPointerException();
  3.     RunnableFuture<Void> ftask = newTaskFor(task, null);
  4.     execute(ftask);
  5.     return ftask;
  6. }
  7. public <T> Future<T> submit(Runnable task, T result) {
  8.     if (task == null) throw new NullPointerException();
  9.     RunnableFuture<T> ftask = newTaskFor(task, result);
  10.     execute(ftask);
  11.     return ftask;
  12. }
  13. public <T> Future<T> submit(Callable<T> task) {
  14.     if (task == null) throw new NullPointerException();
  15.     RunnableFuture<T> ftask = newTaskFor(task);
  16.     execute(ftask);
  17.     return ftask;
  18. }
复制代码
可以看一下上面 submit 的三个重载方法,方法体很相似,都调用了一个方法 newTaskFor(...) ,那么就来看看这个方法,可以看到它有两个重载方法:
  1. protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
  2.     return new FutureTask<T>(runnable, value);
  3. }
  4. protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
  5.     return new FutureTask<T>(callable);
  6. }
复制代码
解释一下上面两个重载方法吧:

  • 第一个newTaskFor(Runnable runnable, T value):可以看到它应该是将 submit 方法传进来的 Runnable 转化成了 Callable,并给一个返回值
  • 第二个newTaskFor(Callable callable):就是submit直接传进了一个 Callable,包装成 FutureTask 返回。
上面代码中可以看一下 RunnableFuture 和 FutureTask 的关系:
  1. 先看一下 RunnableFuture:
  2. public interface RunnableFuture<V> extends Runnable, Future<V> {
  3.     void run();
  4. }
  5. RunnableFuture 实现了 Runnable 和 Future,它的子类就是 FutureTask
  6.    
  7. public class FutureTask<V> implements RunnableFuture<V> {
  8.     // ...
  9. }
  10. 到这里就明白了吧,当 submit 传入的参数是 Runnable 的时候,就需要 FutureTask的构造方法将 Runnable 转化成 Callable
复制代码
下面看一下 FutureTask 的两个构造函数:
  1. // 传入Runnable则是执行了一共方法,看一下这个方法,具体转化逻辑就有了
  2. public FutureTask(Runnable runnable, V result) {
  3.     this.callable = Executors.callable(runnable, result);
  4.     this.state = NEW;       // ensure visibility of callable
  5. }
  6. // 传入Callable直接赋值给类的成员变量
  7. public FutureTask(Callable<V> callable) {
  8.     if (callable == null)
  9.         throw new NullPointerException();
  10.     this.callable = callable;
  11.     this.state = NEW;       // ensure visibility of callable
  12. }
复制代码
下面看一下当 submit 传入 Runnable 的时候,其实到这里就是调用了 FutureTask(Runnable runnable, V result) 构造函数,看一下这个构造函数中将 Runable 转化成了 Callable,看一下 Executors.callable(runnable, result) 方法:
  1. public static <T> Callable<T> callable(Runnable task, T result) {
  2.     if (task == null)
  3.         throw new NullPointerException();
  4.     return new RunnableAdapter<T>(task, result);
  5. }
  6. 看看这里有创建了一个类,就是 RunnableAdapter,下面再看一下这个内部类:
  7.     static final class RunnableAdapter<T> implements Callable<T> {
  8.         final Runnable task;
  9.         final T result;
  10.         RunnableAdapter(Runnable task, T result) {
  11.             this.task = task;
  12.             this.result = result;
  13.         }
  14.         public T call() {
  15.             task.run();
  16.             return result;
  17.         }
  18.     }
  19. 看看这个内部类,实现了 Callable 接口,并在 call 方法中 调用了 task 的 run 方法,就是相当于任务代码直接在 call 方法中了。
复制代码
这里必须说一下线程池中的线程是怎么执行的,这里就不说全部了,直说与这里相关的一部分

  • 看到上面submit 方法最终也是调用了 execute 方法,经过上main源码分析的一系列转换,submit最终调用了ThreadPoolExecutor 的 execute 方法
  • execute 方法里面有一个很关键的方法是 addWorker(command, true)
  • 进入 addWorker 方法,可以看到里面 new 了一个 Worker。Worker 里面有一个属性是 Thread,后面直接调用了它的 start 方法启动了线程
  • 可以看一下 Worker 类,它实现了 Runnable,这里就要看看Worker的run 方法了,调用了 runWorker
  • 在runWorker方法中,有一行是task.run(),调用 submit 时最终这个run方法就是RunnableFuture中的 run() 方法。具体实现在 FutureTask中
下面就看一下 FutureTask 中实现的 run 方法:
  1. public void run() {
  2.     if (state != NEW ||
  3.         !UNSAFE.compareAndSwapObject(this, runnerOffset,
  4.                                      null, Thread.currentThread()))
  5.         return;
  6.     try {
  7.         Callable<V> c = callable;
  8.         if (c != null && state == NEW) {
  9.             V result;
  10.             boolean ran;
  11.             try {
  12.                 //从这里可以看到,调用了call()方法
  13.                 result = c.call();
  14.                 ran = true;
  15.             } catch (Throwable ex) {
  16.                 result = null;
  17.                 ran = false;
  18.                 // 看看,这里是将异常放在了一个属性中,所以 submit执行的时候不会抛出异常,只有在调用 get 方法时才会抛出异常
  19.                 setException(ex);
  20.             }
  21.             if (ran)
  22.                 //这里将返回值设置到了outcome,执行完后可以通过get()获取
  23.                 set(result);
  24.         }
  25.     } finally {
  26.         // runner must be non-null until state is settled to
  27.         // prevent concurrent calls to run()
  28.         runner = null;
  29.         // state must be re-read after nulling runner to prevent
  30.         // leaked interrupts
  31.         int s = state;
  32.         if (s >= INTERRUPTING)
  33.             handlePossibleCancellationInterrupt(s);
  34.     }
  35. }
复制代码
看一下上面两个主要的方法(setException(ex) 和 set(result)):
  1. protected void setException(Throwable t) {
  2.     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
  3.         outcome = t;
  4.         UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
  5.         finishCompletion();
  6.     }
  7. }
  8. protected void set(V v) {
  9.     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
  10.         outcome = v;
  11.         UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
  12.         finishCompletion();
  13.     }
  14. }
复制代码
可以看到两个方法一个是将异常放进了 outcome ,一个是将 call 方法的返回值放进了 outcome。不管是异常还是线程执行的返回值,都会在get 方法中获取到,下面看一下get 方法,方法在 FutureTask 类中:
  1. public V get() throws InterruptedException, ExecutionException {
  2.     int s = state;
  3.     if (s <= COMPLETING)
  4.         s = awaitDone(false, 0L);
  5.     return report(s);
  6. }
  7. private V report(int s) throws ExecutionException {
  8.     Object x = outcome;
  9.     if (s == NORMAL)
  10.         return (V)x;
  11.     if (s >= CANCELLED)
  12.         throw new CancellationException();
  13.     throw new ExecutionException((Throwable)x);
  14. }
复制代码
看看get 方法调用了 report 方法,取到了上面setException(ex) 和 set(result)方法 放进 outcome 的值并返回。
这里如果线程抛出了异常,这个线程会被从线程哈希表中移除,取消强引用,让 GC 回收,并且重新创建一个新的线程。
到这里 submit 方法的源码就分析完了!!!
2.2 execute 源码分析

此方法是线程顶层接口 Executor 定义的,在 ThreadPoolExecutor 有其实现,直接看实现:
其实 submit 方法最终调用了 execute 也是这一段,不同的是最后调用的线程的 run 方法是不同实现类实现的
  1. public void execute(Runnable command) {
  2.         if (command == null)
  3.             throw new NullPointerException();
  4.         /*
  5.          * Proceed in 3 steps:
  6.          *
  7.          * 1. If fewer than corePoolSize threads are running, try to
  8.          * start a new thread with the given command as its first
  9.          * task.  The call to addWorker atomically checks runState and
  10.          * workerCount, and so prevents false alarms that would add
  11.          * threads when it shouldn't, by returning false.
  12.          *
  13.          * 2. If a task can be successfully queued, then we still need
  14.          * to double-check whether we should have added a thread
  15.          * (because existing ones died since last checking) or that
  16.          * the pool shut down since entry into this method. So we
  17.          * recheck state and if necessary roll back the enqueuing if
  18.          * stopped, or start a new thread if there are none.
  19.          *
  20.          * 3. If we cannot queue task, then we try to add a new
  21.          * thread.  If it fails, we know we are shut down or saturated
  22.          * and so reject the task.
  23.          */
  24.         int c = ctl.get();
  25.         if (workerCountOf(c) < corePoolSize) {
  26.             if (addWorker(command, true))
  27.                 return;
  28.             c = ctl.get();
  29.         }
  30.         if (isRunning(c) && workQueue.offer(command)) {
  31.             int recheck = ctl.get();
  32.             if (! isRunning(recheck) && remove(command))
  33.                 reject(command);
  34.             else if (workerCountOf(recheck) == 0)
  35.                 addWorker(null, false);
  36.         }
  37.         else if (!addWorker(command, false))
  38.             reject(command);
  39.     }
复制代码
这里主要就是看 submit 和 execute 的区别点,所以线程池的看具体源码就不看了,我之前写的有一篇线程池源码的笔记很详细:线程池源码
上面有个很重要的方法,是将线程加入队列并执行的,就是 addWorker 方法,这里就不copy addWorker  的代码了,只需要知道里面创建了一个 Worker 对象即可。Worker 对象中有一个属性是 Thread,后面获取到了这个 Thread ,并执行了 start 方法。
然而在 Worker  本身是实现了 Runnable 的,所以后面执行的 start 方法,实际是执行了 Worker 中的 run 方法:
  1. public void run() {
  2.     runWorker(this);
  3. }
复制代码
看看 Worker 中run 方法调用的 runWorker 方法:
  1. final void runWorker(Worker w) {
  2.         Thread wt = Thread.currentThread();
  3.         Runnable task = w.firstTask;
  4.         w.firstTask = null;
  5.         w.unlock(); // allow interrupts
  6.         boolean completedAbruptly = true;
  7.         try {
  8.             while (task != null || (task = getTask()) != null) {
  9.                 w.lock();
  10.                 // If pool is stopping, ensure thread is interrupted;
  11.                 // if not, ensure thread is not interrupted.  This
  12.                 // requires a recheck in second case to deal with
  13.                 // shutdownNow race while clearing interrupt
  14.                 if ((runStateAtLeast(ctl.get(), STOP) ||
  15.                      (Thread.interrupted() &&
  16.                       runStateAtLeast(ctl.get(), STOP))) &&
  17.                     !wt.isInterrupted())
  18.                     wt.interrupt();
  19.                 try {
  20.                     beforeExecute(wt, task);
  21.                     Throwable thrown = null;
  22.                     try {
  23.                         // 这个地方是重点,run 方法
  24.                         task.run();
  25.                     } catch (RuntimeException x) {
  26.                         thrown = x; throw x;
  27.                     } catch (Error x) {
  28.                         thrown = x; throw x;
  29.                     } catch (Throwable x) {
  30.                         thrown = x; throw new Error(x);
  31.                     } finally {
  32.                         afterExecute(task, thrown);
  33.                     }
  34.                 } finally {
  35.                     task = null;
  36.                     w.completedTasks++;
  37.                     w.unlock();
  38.                 }
  39.             }
  40.             completedAbruptly = false;
  41.         } finally {
  42.             processWorkerExit(w, completedAbruptly);
  43.         }
  44.     }
复制代码
其实这里的源码就和 submit 一样了,只是上面 task.run() 调用的是不同实现类的 run 方法。

  • execute  方法传进来的最终是调用的 Runnable 或其子类的run 方法
  • submit 方法进来的最终是调用了 FutureTask 的run 方法
基于上面的区别,再去看 FutureTask 中 run 方法的源码,就可以知道一下结论:

  • execute  是没返回值的,submit 有返回值
  • 用 execute 提交任务时,任务内有异常会直接打印出来;用 submit 提交任务,任务内有异常也不会打印异常信息,而是调用get()方法时,打印出任务执行异常信息

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表