多线程异常了会怎样?

2,102 阅读5分钟

前言

  • 之前看过一个文章,里面有一个面试题,是关于多线程的问题。
  • 问题描述的是:如果一个线程死了,会发生什么?
  • 个人在没有看文章后续讲解时,当时心里产生了几个答案。可惜最后发现没有回答到点上,所以打算记录一下 -_-

分析过程

  • 首先定义一个ThreadPoolExecutor线程池。再往里面添加两个线程。
    private final static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());

    public static void main(String[] args) {
        test();
    }

    private static void test() {
        EXECUTOR.execute(() -> doIt("execute"));
        EXECUTOR.submit(() -> doIt("submit"));
    }

    private static void doIt(String name) {
        System.out.println(name + " 执行了");
        throw new RuntimeException(name + " 异常了");
    }
  • 执行结果是
execute 执行了
submit 执行了
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: execute 异常了
	at com.web.test.test20210226.Test1.doIt(Test1.java:26)
	at com.web.test.test20210226.Test1.lambda$test$0(Test1.java:20)
	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)
  • 这里我们得出的结论:
  • 这里往线程池添加了两个线程,抛异常后线程没有互相影响。
  • 线程池的execute()方法会直接抛出异常信息,而这里的submit()方法没有抛出异常信息。
  • 但是submit()方法后面一般都会跟着future.get()方法来获取线程执行结果,现在线程抛异常了,那么得到的执行结果会是什么呢?
private final static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>());

    public static void main(String[] args) {
        test();
    }

    private static void test() {
        Future<?> future = EXECUTOR.submit(() -> doIt("submit"));
        try {
            future.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void doIt(String name) {
        System.out.println(name + " 执行了");
        throw new RuntimeException(name + " 异常了");
    }
  • 执行结果:
submit 执行了
java.util.concurrent.ExecutionException: java.lang.RuntimeException: submit 异常了
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.web.test.test20210226.Test1.test(Test1.java:23)
	at com.web.test.test20210226.Test1.main(Test1.java:17)
Caused by: java.lang.RuntimeException: submit 异常了
	at com.web.test.test20210226.Test1.doIt(Test1.java:31)
	at com.web.test.test20210226.Test1.lambda$test$0(Test1.java:21)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	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)
  • 这里在调用future.get()方法后,也抛出异常信息。所以可以得出结论:
  • 线程池执行submit()方法没有直接抛出异常信息,而如果调用future.get()方法才会抛出异常信息。
  • 根据打印的异常信息,里面都打印了 at.java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)。点进去看一下ThreadPoolExecutor源码。
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 {
        	// 线程池中worker的回收机制实现。
            processWorkerExit(w, completedAbruptly);
        }
    }
  • 原理可以看到在task.run()方法异常后,在catch语句里直接把抛出异常信息。
  • 然后看processWorkerExit(w, completedAbruptly)方法。这个方式是当线程异常中断后,对worker的回收机制实现。
/**
     * Performs cleanup and bookkeeping for a dying worker. Called
     * only from worker threads. Unless completedAbruptly is set,
     * assumes that workerCount has already been adjusted to account
     * for exit.  This method removes thread from worker set, and
     * possibly terminates the pool or replaces the worker if either
     * it exited due to user task exception or if fewer than
     * corePoolSize workers are running or queue is non-empty but
     * there are no workers.
     *
     * @param w the worker
     * @param completedAbruptly if the worker died due to user exception
     */
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            // 这里移除掉之前的异常中断的线程
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            // 重新添加一个线程到队列里去
            addWorker(null, false);
        }
    }

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

  • 所以这里得出结论:当线程抛异常后,线程不会回收到线程池而是直接移除线程,并且创建一个新的线程放入线程池。
  • 在来看看submit(Runnable task)方法执行源码。传入的参数会被封装成一个FutureTask对象,并且也是执行的execute()方法。
    /**
     * @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;
    }
    -----------------------------
	/**
     * Returns a {@code RunnableFuture} for the given runnable and default
     * value.
     *
     * @param runnable the runnable task being wrapped
     * @param value the default value for the returned future
     * @param <T> the type of the given value
     * @return a {@code RunnableFuture} which, when run, will run the
     * underlying runnable and which, as a {@code Future}, will yield
     * the given value as its result and provide for cancellation of
     * the underlying task
     * @since 1.6
     */
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
  • 看看FutureTask对象执行的run()方法源码
/**
     * Causes this future to report an {@link ExecutionException}
     * with the given throwable as its cause, unless this future has
     * already been set or has been cancelled.
     *
     * <p>This method is invoked internally by the {@link #run} method
     * upon failure of the computation.
     *
     * @param t the cause of failure
     */
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        	// t 是run()方法中保存的异常信息
            outcome = t;
            // 为变量state赋值为EXCEPTIONAL(3)
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }
    
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;
                    // 这里是重点,保存异常信息并修改变量state值
                    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);
        }
    }
    ----------------------------------------
    /**
     * The run state of this task, initially NEW.  The run state
     * transitions to a terminal state only in methods set,
     * setException, and cancel.  During completion, state may take on
     * transient values of COMPLETING (while outcome is being set) or
     * INTERRUPTING (only while interrupting the runner to satisfy a
     * cancel(true)). Transitions from these intermediate to final
     * states use cheaper ordered/lazy writes because values are unique
     * and cannot be further modified.
     *
     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
    private volatile int state;
    private static final int NEW          = 0; // 新建初始状态
    private static final int COMPLETING   = 1; // 运行
    private static final int NORMAL       = 2; // 正常
    private static final int EXCEPTIONAL  = 3; // 异常
    private static final int CANCELLED    = 4; // 取消
    private static final int INTERRUPTING = 5; // 中断中
    private static final int INTERRUPTED  = 6; // 被中断
  • 在catch语句里,没有直接抛出异常信息,而是调用了setException(ex)方法保存异常信息。并且把变量state赋值为EXCEPTIONAL
  • 在看一下fufuretask.get()方法源码执行流程。
	/**
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
        	// 这里当变量state的状态大于1,则直接返回state。所以这里返回setException()方法里修改的值3
            s = awaitDone(false, 0L);
        return report(s);
    }
    -------------------------
    /**
     * Returns result or throws exception for completed task.
     *
     * @param s completed state value
     */
    @SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        // 当变量state的值为3时,抛出异常
        throw new ExecutionException((Throwable)x);
    }
  • 在report()方法里直接抛出ExecutionException和之前保存的异常信息。

最后

  • 得出的结论是:
    • 如果是用execute方法执行线程,那么会直接抛异常:如果是用submit方法提交线程,不会直接打印异常,而是需要调用future.get()方法才会打印异常。
    • 当线程抛异常后不会影响其他线程。
    • 当线程抛异常后,线程不会回收到线程池而是直接移除线程,并且创建一个新的线程放入线程池中。
  • 虚心学习,共同进步 -_-
  • 参考文章链接