为什么java线程池的submit的不抛出异常

676 阅读2分钟

前言

大家好,这里是经典鸡翅,最近有人问我线程池的execute和submit,有的抛出异常,有的不抛出异常,这里鸡翅老哥给大家整理下。通过源码跟踪跟大家讲解。

两个方法

我们先用最普通的方式定义一个线程池。

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024), new ThreadPoolExecutor.AbortPolicy());

线程池有两种执行任务的方式,一种是execute方法,一种是submit方法。

我们引入一个最简单的例子。执行一个有异常的方法。

    @Test
    public void test() throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024), new ThreadPoolExecutor.AbortPolicy());
        threadPoolExecutor.execute(() -> {
            testThread();
        });
    }

    private int testThread() {
        int i = 1 / 0;
        return i;
    }

如上的一段代码,由于1/0的存在,必定是会出现异常的,我们执行后,看到结果,是有异常的。符合我们的预期。

Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
	at com.hq.hqframe.textUtil.TextUtilTest.testThread(TextUtilTest.java:104)
	at com.hq.hqframe.textUtil.TextUtilTest.lambda$test$5(TextUtilTest.java:99)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

如果我们使用submit提交任务呢?

    @Test
    public void test() throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024), new ThreadPoolExecutor.AbortPolicy());
        threadPoolExecutor.submit(() -> {
            testThread();
        });
    }

    private int testThread() {
        int i = 1 / 0;
        return i;
    }

执行代码我们发现,控制台并没有打印出任何异常。异常被吞了。这就是execute和submit的第一个区别。

为什么submit不会抛出异常呢?

我们开始跟踪一下,为什么submit不会抛出异常。先看submit的第一个方法。

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

将task进行包装为FutureTask。这里面的task是runnable的,我们将其包装成callable形式的。通过runnableadapter。

    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

包装后,执行这个task。

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

我们直接将目光聚焦到addWorker,这里面干了一个什么事呢?重点建造了一个Worker类。把当前的task传入进去。Worker类里面把自己的task作为一个线程赋值。

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

回到addWorker处,我们可以看到start方法,这个方法,实际调用的就是线程的run方法。也就是我们worker类里面自己实现的run方法。

        public void run() {
            runWorker(this);
        }

再看下runworker方法,我们发现里面继续调用了task.run方法。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

我们就可以重新追踪到futureTask。

    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 must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }

此时我们发现,异常已经被捕获了,并且调用了setexception方法。把我们的异常信息给了outcome。

    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

到此我们就明白了为什么submit没有抛出异常了!