1、submit坑

此处随便写一个方法,进入内部查看execute和submit

  1. /**
  2. * @Author: 小混蛋
  3. * @CreateDate: 2018/8/29 9:58
  4. */
  5. @Component
  6. public class Test {
  7. public static void main(String[] args) {
  8. ExecutorService es = Executors.newFixedThreadPool(5);
  9. ArrayList<Future<?>> arrayList = new ArrayList();
  10. for (int i = 0; i < 10; i++) {
  11. final int b = i;
  12. Future<?> submit = es.submit(() -> {
  13. System.out.println(Thread.currentThread().getName());
  14. int a = b / 0;
  15. });
  16. arrayList.add(submit);
  17. }
  18. arrayList.forEach(s -> {
  19. try {
  20. s.get();
  21. } catch (InterruptedException |ExecutionException e) {
  22. e.printStackTrace();
  23. }
  24. });
  25. es.shutdown();
  26. }
  27. @Scheduled(cron = "")
  28. public void test() {
  29. }
  30. }

ctrl加鼠标左键进入submit,查看AbstractExecutorService,发现submit底层调用的还是execute,但是提交的任务不是task,而是在task的基础上封装了一层FutureTask

  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. }

重点来了,当submit提交的task里面出现未检查异常如RuntimeException和Error等,直接execute你的task肯定是抛异常;但是使用submit之后提交的FutureTask我们看下它的源码run方法:run方法和我们直接提交的task的run方法并不一样,该方法会对所有的Throwable类型进行捕获,并把异常通过setException保存在内部变量outcome里面。所以线程池执行的过程中异常不会被抛出

  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. result = c.call();
  13. ran = true;
  14. } catch (Throwable ex) {
  15. result = null;
  16. ran = false;
  17. setException(ex);
  18. }
  19. if (ran)
  20. set(result);
  21. }
  22. } finally {
  23. runner = null;
  24. int s = state;
  25. if (s >= INTERRUPTING)
  26. handlePossibleCancellationInterrupt(s);
  27. }
  28. }
  29. protected void setException(Throwable t) {
  30. if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
  31. outcome = t;
  32. UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
  33. finishCompletion();
  34. }
  35. }

另一个重点来了,当submit被futuretask.get的时候。会在report方法调用过程中抛出这个未检查异常!

  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. }

结论

1、submit在执行过程中与execute不一样,不会抛出异常而是把异常保存在成员变量中,在FutureTask.get阻塞获取的时候再把异常抛出来。
2、Spring的@Schedule注解的内部实现就是使用submit,因此,如果你构建的任务内部有未检查异常,你是永远也拿不到这个异常的。
3、execute直接抛出异常之后线程就死掉了,submit保存异常线程没有死掉,因此execute的线程池可能会出现没有意义的情况,因为线程没有得到重用。而submit不会出现这种情况。