FixedThreadPool吞掉了异常

原创
2019/04/27 21:06
阅读数 446

FixedThreadPool吞掉了异常

为了方便遍描述问题,如下是简化后的

public class RunException {
    public static void main(String[] args) {

        ExecutorService readerPool = Executors.newFixedThreadPool(3);
        readerPool.submit(new Runnable() {
            public void run() {
               throw new RuntimeException("异常");
            }
        });

        readerPool.shutdown();
    }
}

此处FixedThreadPool吞掉了异常。

问题

  1. 为什么不能抛出到外部线程捕获
  2. submit为什么不能打印报错信息
  3. execute怎么使用logger打印报错信息

为什么不能抛出到外部线程捕获

jvm会在线程即将死掉的时候捕获所有未捕获的异常进行处理。默认使用的是Thread.defaultUncaughtExceptionHandler

submit为什么不能打印报错信息

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

查看FutureTask.run():

   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
                    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(ex);,将线程状态由completing改为exceptional,并将异常信息存在outcome中:

    //这个方法就是这事线程状态为completing -> exceptional
    //同时用outcome保存异常信息。
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

继续查看outcome的使用:

//report会抛出exception信息
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);
    }

//get会调用report()方法
public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
  1. report会抛出exception信息,但report是私有方法;
  2. get会调用report()方法

所以如果需要获取异常信息就需要调用get()方法。

execute怎么输入logger日志

查看execute的实现ThreadPoolExecutor.execute()

 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);
    }

从代码可知,线程池将任务加入了任务队列,需要看看线程在哪执行任务的。那么只需要看看有没有获取任务的函数,ThreadPoolExecutor.getTask()即是获取任务的函数,通过查找,ThreadPoolExecutor.runWorker调用了ThreadPoolExecutor.getTask(),它应该是执行任务的代码:

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) {
                         //这里直接抛出所有Runtime异常
                        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);
        }
    }

代码注释中看到获取RuntimeException的位置了。

这里抛出的异常在哪里处理呢? 接下来处理是交由jvm处理,从已经学习的知识中只知道jvm调用Thread.dispatchUncaughtException来处理所有未捕获的异常

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

这里可以根据该方法注释解释,意思就是这个方法只用于JVM调用,处理线程未捕获的异常。 继续查看getUncaughtExceptionHandler()方法:

    public interface UncaughtExceptionHandler {s
        void uncaughtException(Thread t, Throwable e);
    }

    // 处理类
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

    // 默认处理类
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    /**
    * 设置默认的处理类,注意是静态方法,作用域为所有线程设置默认的处理类
    **/
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(
                new RuntimePermission("setDefaultUncaughtExceptionHandler")
                    );
        }

         defaultUncaughtExceptionHandler = eh;
     }
    //获取默认处理类
    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
        return defaultUncaughtExceptionHandler;
    }
    //获取处理类,注意不是静态方法,只作用域该线程
    //处理类为空使用ThreadGroup
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
    //设置处理类
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        //获取处理类型进行异常处理
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

如果线程UncaughtExceptionHandler处理器为空则threadGroup处理器 查看threadGroup:

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

从代码中可以看出,

  1. 如果父进程不为空,则使用父进程处理未捕获异常;
  2. 如果无父进程,则获取默认的UncaughtExceptionHandler进行处理。
    1. 默认的UncaughtExceptionHandler为null,则使用Sytem.err将错误信息输出;
    2. 默认的UncaughtExceptionHandler不为null,则使用UncaughtExceptionHandler进行处理。

所以有两个方法实现用logger输出:

  1. Thread定义uncaughtExceptionHandlerThread.setUncaughtExceptionHandler(),该方法仅能设置某个线程的默认UncaughtExceptionHandler
  2. Thread定义defaultUncaughtExceptionHandler:使用Thread.setDefaultUncaughtExceptionHandler,该方法设置所有线程的默认UncaughtExceptionHandler

测试程序

仅某个线程设置默认UncaughtExceptionHandler

public static void oneThreadUncaughtExceptionHandler() {
        Thread t1 = new Thread(() -> {
            throw new RuntimeException(" t1 runtime exception");
        }, "t1");
        t1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(Thread.currentThread() + "trigger uncaugh exception handler");
            }
        });

        t1.start();
        Thread t2 = new Thread(() -> {
            throw new RuntimeException(" t2 runtime exception");
        }, "t2");
        t2.start();
    }

设置defaultUncaughtExceptionHandler

public static void defaultThreadUncaughtExceptionHandler() {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(Thread.currentThread() + "trigger uncaugh exception handler");
            }
        });
        new Thread(() -> {
            throw new RuntimeException(" t1 runtime exception");
        }, "t1").start();
        new Thread(() -> {
            throw new RuntimeException(" t2 runtime exception");
        }, "t2").start();
    }

解惑

那为什么我们的例子代码中,异常不会输出呢?应该有兜底的System.err来输出异常才对。 不是这样的,我们的例子中的异常实际上是处理了的,它捕获了异常,并且保存到了outcome中。仅仅有未捕获的异常,JVM才会调用Thread.dispatchUncaughtException来处理。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部