ThreadPoolExecutor是如何处理任务的异常情况

2023-11-19

本文因生产环境线程池某些场景下的任务异常后,日志文件中没有被记录进来产生的困惑引发的思考。
当然如果所有异步的业务方法run里面都加上一层try…catch…就可以主动捕获所有的异常,也能够记录到日志文件中,然而总有一些人总有一些时候不小心漏掉了,今天分享下run方法如果不加try…catch…的后果

测试调用execute

//测试代码
public static void testExecute() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        pool.execute(() -> {

            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });

    }
}

//测试结果  控制台输出
2020-06-04 00:00:50.118 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc0
2020-06-04 00:00:50.122 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc1
2020-06-04 00:00:50.122 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc2
2020-06-04 00:00:51.837 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         91 lzc4
Exception in thread "pool-1-thread-1" 
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$testExecute$2(ExceptionPoolDemo.java:89)
	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)

//测试结果  日志文件输出
2020-06-04 00:00:50.118 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc0
2020-06-04 00:00:50.122 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc1
2020-06-04 00:00:50.122 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         91 lzc2
2020-06-04 00:00:51.837 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         91 lzc4

测试结论

1.控制台会有异常堆栈信息 , 然而日志文件中却没有记录这条异常,这个很致命,异步处理的线程出现了异常,日志没有记录事件,事后很难排查
2.corePoolSize设置的是1 , blockinqueue使用的是无界队列,正常情况下,始终只会有一个线程来所有任务,即pool-1-thread-1,然而从上面的日志可以发现有新线程pool-1-thread-2参加工作了

测试调用submit

//测试代码
public static void testSubmit() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        pool.submit(() -> {

            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });



    }
}

//测试结果  控制台输出

2020-06-04 00:15:25.069 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc0
2020-06-04 00:15:25.072 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc1
2020-06-04 00:15:25.072 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc2
2020-06-04 00:15:25.073 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc4

//测试结果  日志文件输出
2020-06-04 00:15:25.069 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc0
2020-06-04 00:15:25.072 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc1
2020-06-04 00:15:25.072 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc2
2020-06-04 00:15:25.073 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         112 lzc4

测试结论

1.这个更要命,控制台与日志文件都没有记录这条异常,后果同上
2.submit时没有使用新工作线程,使用始终使用的是pool-1-thread-1

测试调用submit+future.get()

//测试代码
public static void testSubmitFuture() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    List<Future> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        int finalI = i;
        Future f = pool.submit(() -> {
            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });
        list.add(f);
    }

    for (Future f : list) {
        try {
            f.get();
        } catch (Exception e) {
            logger.error("出现了异常", e);
        }
    }

}

//测试结果  控制台输出

2020-06-04 00:18:55.784 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc0
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc1
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc2
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc4
2020-06-04 00:18:55.790 ERROR main com.alioo.pool.ExceptionPoolDemo         144 出现了异常
java.util.concurrent.ExecutionException: java.lang.RuntimeException: abcdefg
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.alioo.pool.ExceptionPoolDemo.testSubmitFuture(ExceptionPoolDemo.java:142)
	at com.alioo.pool.ExceptionPoolDemo.main(ExceptionPoolDemo.java:153)
Caused by: java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$testSubmitFuture$4(ExceptionPoolDemo.java:132)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	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)

//测试结果  日志文件输出
2020-06-04 00:18:55.784 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc0
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc1
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc2
2020-06-04 00:18:55.787 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         134 lzc4
2020-06-04 00:18:55.790 ERROR main com.alioo.pool.ExceptionPoolDemo         144 出现了异常
java.util.concurrent.ExecutionException: java.lang.RuntimeException: abcdefg
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.alioo.pool.ExceptionPoolDemo.testSubmitFuture(ExceptionPoolDemo.java:142)
	at com.alioo.pool.ExceptionPoolDemo.main(ExceptionPoolDemo.java:153)
Caused by: java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$testSubmitFuture$4(ExceptionPoolDemo.java:132)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	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)

测试结论

1.future.get()表示接收返回值,当调用future.get()后,控制台与日志文件均记录下来了,完美!
2.submit时没有使用新工作线程,使用始终使用的是pool-1-thread-1

原因分析

原因分析 execute

线程池的工作线程类Worker实现了Runnable接口,接下来主要分析Worker的构造方法与run方法

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

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

解码:

  1. Worker的构造方法中调用getThreadFactory().newThread(this);通过工厂方法来创建工作线程
  2. 上面的测试代码创建pool时使用的是MyThreadFactory,代码在文章最后,上面测试的案例使用的MyThreadFactory跟Executors.defaultThreadFactory()一模一样
  3. 创建线程前被要求先创建一个group,阅读代码会发现group其实是Thread.currentThread().getThreadGroup(),ThreadGroup实现了Thread.UncaughtExceptionHandler接口
Thread t = new Thread(group, r,
        namePrefix + threadNumber.getAndIncrement(),
       0);
  1. 分析下ThreadGroup.uncaughtException方法,当线程出现未捕获的异常时就会进入到这里,看代码有点类似于双亲委派机制,优先交给parent来处理异常。
    实际断点发现会进入2次这个方法,第1次由于parent不为空(parent:java.lang.ThreadGroup[name=system,maxpri=10]),第2次为空
    第2次进入后,ueh为null,所以执行了else if代码块,结果跟上面测试execute会发现是可以对上的

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. 前面几步分析是有个前提的,就是当Worker线程出现了未捕获的异常才能走上面的uncaughtException方法,虽然我们之前交待了业务方法出现了异常未捕获,但是还要进一步看看Worker.run()方法是否会帮我们try…catch…
    (这里也很关键,也是这里的代码差异造成了上面3种不同的结果)
    Worker.run()方法直接调用了runWorker()方法

    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;         //第1层try...块,业务逻辑会抛出RuntimeException,所以会进入到这里,这里虽然捕获了,然而又throw了
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {                       //第2层try...块,这里的try...块没有catch,是不是很惊喜,收到第1个层throw出现的RuntimeException只需要做finally逻辑,然后RuntimeException继续往外抛
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {                             //第3层try...块,这里的try...块依旧没有catch,收到第2层throw出现的RuntimeException依旧只需要做finally逻辑,继续往外抛RuntimeException
                                                //然而已经到最外层了,还没有被catch住的话就会交给Thread.currentThread().getThreadGroup().uncaughtException进行处理了。
            processWorkerExit(w, completedAbruptly);
        }
    }

原因分析 submit

前面已经铺垫了Worker.run()方法代码差异造成了submit后异常处理的不同,接下来具体分析下吧

  1. submit方法调用时对原始的业务方法对象task进行了包装,生成了新的Rannable对象FutureTask

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

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}
    
  1. 下面是Rannable对象FutureTask对象的run方法,会在这个里面调用原始的业务方法```c.call()``

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();             //原始的业务方法call()会在这里进行调用
                ran = true;
            } catch (Throwable ex) {
                result = null;                //第1层try...块,这里的try...块遇到RuntimeException直接吃掉了,并没有继续往外抛
                ran = false;
                setException(ex);             //进入这个方法会将ex赋值到outcome对象中(注意正常情况下outcome存放的是方法返回值result)
            }
            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)                    //第2层try...块,当出现异常时,这里的if不会进去执行
            handlePossibleCancellationInterrupt(s);
    }                                            
}


protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}
    
  1. 当整个run方法执行完成后,异常存放到了outcome属性中,并没有任何地方打印输出,所以跟前面测试submit情况也是符合的

原因分析 submit+future.get()

前面的测试结论中可以看到只调用submit时异常信息是没有任何输出的,而当调用了future.get()异常就出来了,这是为什么呢,下面对着源码来揭晓答案

  1. 由前面的代码我们知道Future实现是FutureTask,查看下FutureTask.get()。异常发生时state= (EXCEPTIONAL = 3),这个方法中的if不会进去,直接会调用report(s)
    report(s)中的2个if也不满足条件,也不会执行,最终就会执行throw new ExecutionException((Throwable)x);,好了,这就是只调用submit不会输出异常,而当进一步调用future.get()就会触发往外抛异常了

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;

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

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

  1. 由于future.get()方法强制要求调用方捕获异常,所以我们在调用的地方使用了try…catch…,前面测试时看到的异常日志都来源自这里的logger.error("出现了异常", e);
for (Future f : list) {
    try {
        f.get();
    } catch (Exception e) {
        logger.error("出现了异常", e);
    }
}
    
  1. 细心的你或许会发现这个地方的异常堆栈比较奇怪,先打印了ExecutionException,后来又打印了RuntimeException,这里因为report方法中对原始异常RuntimeException又包了一层ExecutionException
    本质上还是只有1个异常的,这样做是有好处的既告诉了你你捕获的异常是从哪里发起的ExecutionException,又告诉了你原始异常发生的地方
2020-06-04 00:18:55.790 ERROR main com.alioo.pool.ExceptionPoolDemo         144 出现了异常
java.util.concurrent.ExecutionException: java.lang.RuntimeException: abcdefg
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.alioo.pool.ExceptionPoolDemo.testSubmitFuture(ExceptionPoolDemo.java:142)
	at com.alioo.pool.ExceptionPoolDemo.main(ExceptionPoolDemo.java:153)
Caused by: java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$testSubmitFuture$4(ExceptionPoolDemo.java:132)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	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)


附文中使用了自定义的ThreadFactory,实现与Executors.defaultThreadFactory()一模一样

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class MyThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    public  MyThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
            Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
            namePrefix + threadNumber.getAndIncrement(),
            0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

兜底解决方案

上面分别分析了execute,submit在业务方法未捕获异常时的表现,当异常发生时不能记录到日志文件中后续定位问题是非常麻烦的,那么作为线程框架可以有哪些解决手段呢?

业务方法自行try…catch…,首选方案吧,强烈建议这样子做

runWorker()方法中会调用afterExecute(task, thrown),可以在重写这个方法对异常进行日志记录

这里需要说明的是重写了afterExecute(task, thrown)只是增加了对异常的日志打印,但是异常还是会继续往外抛出,所以控制台会打印2次异常日志,但是日志文件只会打印1次(未重写afterExecute时,控制台打印1次,日志文件0次)

//测试代码

public static void afterExecute() {
    ThreadPoolExecutor executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>()) {
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
            if (t != null) {
                logger.error("出现了异常", t);
            }
        }
    };

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        executorService.execute(() -> {

            if (finalI == 3) {
                throw new RuntimeException("aabb");
            }
            logger.info("lzc" + finalI);

        });

    }

}

//测试结果  控制台输出
2020-06-04 12:51:42.070 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc0
2020-06-04 12:51:42.073 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc1
2020-06-04 12:51:42.073 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc2
2020-06-04 12:51:55.549 ERROR pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         23 出现了异常
java.lang.RuntimeException: aabb
	at com.alioo.pool.ExceptionPoolDemo.lambda$afterExecute$0(ExceptionPoolDemo.java:33)
	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)
2020-06-04 12:51:55.552 INFO  pool-2-thread-2 com.alioo.pool.ExceptionPoolDemo         35 lzc4
Exception in thread "pool-2-thread-1" java.lang.RuntimeException: aabb
	at com.alioo.pool.ExceptionPoolDemo.lambda$afterExecute$0(ExceptionPoolDemo.java:33)
	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)


//测试结果  日志文件输出
2020-06-04 12:51:42.070 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc0
2020-06-04 12:51:42.073 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc1
2020-06-04 12:51:42.073 INFO  pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         35 lzc2
2020-06-04 12:51:55.549 ERROR pool-2-thread-1 com.alioo.pool.ExceptionPoolDemo         23 出现了异常
java.lang.RuntimeException: aabb
	at com.alioo.pool.ExceptionPoolDemo.lambda$afterExecute$0(ExceptionPoolDemo.java:33)
	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)
2020-06-04 12:51:55.552 INFO  pool-2-thread-2 com.alioo.pool.ExceptionPoolDemo         35 lzc4

自定义ThreadGroup

这里使用了自定义的MyThreadFactory,在MyThreadFactory中创建线程时使用自定义的MyThreadGroup,并且重写了uncaughtException方法,当遇到异常时记录到日志文件里

//测试代码
public static void myThreadGroup() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        pool.execute(() -> {

            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });

    }
}



public class MyThreadFactory implements ThreadFactory {

    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    public MyThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = new MyThreadGroup("mythreadgroup");
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
            namePrefix + threadNumber.getAndIncrement(),
            0);
        if (t.isDaemon()) { t.setDaemon(false); }
        if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); }
        return t;
    }
}

class MyThreadGroup extends ThreadGroup {
    private static Logger logger = LoggerFactory.getLogger(MyThreadGroup.class);

    public MyThreadGroup(String name) {
        super(name);
    }

    @Override
    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)) {
            logger.error("兄弟,你的业务方法出现异常咋不处理呢,Exception in thread \""
                + t.getName() + "\" ",e);

        }
        //}
    }
}

//测试结果  控制台输出

2020-06-04 13:59:37.517 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc0
2020-06-04 13:59:37.522 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc1
2020-06-04 13:59:37.522 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc2
2020-06-04 13:59:37.618 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         55 lzc4
2020-06-04 13:59:37.622 ERROR pool-1-thread-1 com.alioo.pool.MyThreadGroup             52 兄弟,你的业务方法出现异常咋不处理呢,Exception in thread "pool-1-thread-1" 
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$uncaughtExceptionHandler$1(ExceptionPoolDemo.java:53)
	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)


//测试结果  日志文件输出
2020-06-04 13:59:37.517 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc0
2020-06-04 13:59:37.522 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc1
2020-06-04 13:59:37.522 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         55 lzc2
2020-06-04 13:59:37.618 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         55 lzc4
2020-06-04 13:59:37.622 ERROR pool-1-thread-1 com.alioo.pool.MyThreadGroup             52 兄弟,你的业务方法出现异常咋不处理呢,Exception in thread "pool-1-thread-1"
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$uncaughtExceptionHandler$1(ExceptionPoolDemo.java:53)
	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)
	
	

设置UncaughtExceptionHandler

跟上面的方法类似,都是重写ThreadFactory,上面是在创建线程时指定ThreadGroup,现在是线程按照默认方式创建完了,重置下ThreadGroup,仅此而已。

//测试代码
public static void uncaughtExceptionHandler() {

    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS,
        new LinkedBlockingQueue<>(), new MyThreadFactory2(), new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 5; i++) {
        int finalI = i;
        pool.execute(() -> {

            if (finalI == 3) {
                throw new RuntimeException("abcdefg");
            }
            logger.info("lzc" + finalI);

        });

    }
}

public class MyThreadFactory2 implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    MyThreadFactory2() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
            Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
            namePrefix + threadNumber.getAndIncrement(),
            0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);

        t.setUncaughtExceptionHandler(new MyThreadGroup2("mythreadgroup"));
        return t;
    }
}

class MyThreadGroup2 extends ThreadGroup {
    private static Logger logger = LoggerFactory.getLogger(MyThreadGroup.class);

    public MyThreadGroup2(String name) {
        super(name);
    }

    @Override
    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)) {
            logger.error("兄弟,你的业务方法出现异常咋不处理呢,Exception in thread \""
                + t.getName() + "\" ",e);

        }
        //}
    }
}

//测试结果  控制台输出
2020-06-04 14:17:57.439 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc0
2020-06-04 14:17:57.443 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc1
2020-06-04 14:17:57.443 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc2
2020-06-04 14:17:58.047 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         74 lzc4
2020-06-04 14:17:58.049 ERROR pool-1-thread-1 com.alioo.pool.MyThreadGroup             55 兄弟,你的业务方法出现异常咋不处理呢,Exception in thread "pool-1-thread-1" 
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$uncaughtExceptionHandler$2(ExceptionPoolDemo.java:72)
	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)

	
//测试结果  日志文件输出
2020-06-04 14:17:57.439 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc0
2020-06-04 14:17:57.443 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc1
2020-06-04 14:17:57.443 INFO  pool-1-thread-1 com.alioo.pool.ExceptionPoolDemo         74 lzc2
2020-06-04 14:17:58.047 INFO  pool-1-thread-2 com.alioo.pool.ExceptionPoolDemo         74 lzc4
2020-06-04 14:17:58.049 ERROR pool-1-thread-1 com.alioo.pool.MyThreadGroup             55 兄弟,你的业务方法出现异常咋不处理呢,Exception in thread "pool-1-thread-1"
java.lang.RuntimeException: abcdefg
	at com.alioo.pool.ExceptionPoolDemo.lambda$uncaughtExceptionHandler$2(ExceptionPoolDemo.java:72)
	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)
	

参考文章
https://www.cnblogs.com/Laymen/p/11465881.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ThreadPoolExecutor是如何处理任务的异常情况 的相关文章

  • 如何在一行中将字符串数组转换为双精度数组

    我有一个字符串数组 String guaranteedOutput Arrays copyOf values values length String class 所有字符串值都是数字 数据应转换为Double QuestionJava 中
  • 如何测试 JUnit 测试的 Comparator?

    我需要测试 Compare 方法 但我对如何测试感到困惑 我可以看看该怎么做吗 public class MemberComparator implements Comparator
  • JNI 不满意链接错误

    我想创建一个简单的 JNI 层 我使用Visual studio 2008创建了一个dll Win 32控制台应用程序项目类型 带有DLL作为选项 当我调用本机方法时 出现此异常 Exception occurred during even
  • ExceptionConverter:java.io.IOException:文档没有页面。我正在使用 iText

    当我执行下面的代码时 File f new File c sample pdf PdfWriter getInstance document new FileOutputStream f document open System out p
  • IntelliJ IDEA 创建的 JAR 文件无法运行

    我在 IntelliJ 中编写了一个跨越几个类的程序 当我在 IDE 中测试它时它运行良好 但是 每当我按照教程将项目制作成 jar 可执行文件时 它就不会运行 双击 out 文件夹中的文件时 该文件不会运行 并显示 无法启动 Java J
  • CXF Swagger2功能添加安全定义

    我想使用 org apache cxf jaxrs swagger Swagger2Feature 将安全定义添加到我的其余服务中 但是我看不到任何相关方法或任何有关如何执行此操作的资源 下面是我想使用 swagger2feature 生成
  • 如何在 Java 中禁用 System.out 以提高速度

    我正在用 Java 编写一个模拟重力的程序 其中有一堆日志语句 到 System out 我的程序运行速度非常慢 我认为日志记录可能是部分原因 有什么方法可以禁用 System out 以便我的程序在打印时不会变慢 或者我是否必须手动检查并
  • 如何为 Gson 编写自定义 JSON 反序列化器?

    我有一个 Java 类 用户 public class User int id String name Timestamp updateDate 我收到一个包含来自 Web 服务的用户对象的 JSON 列表 id 1 name Jonas
  • hibernate总是自己删除表中的所有数据

    您好 我正在开发一个 spring mvc 应用程序 它使用 hibernate 连接到存储文件的 mysql 数据库 我有两个方法 一个方法添加我选择的特定文件路径中的所有文件 另一种方法调用查询以返回从 mysql 存储的文件列表 问题
  • 如何在jsp代码中导入java库?

    我有以下jsp代码 我想添加 java io 等库 我怎样才能做到这一点
  • 请求位置更新参数

    这就是 requestLocationUpdates 的样子 我使用它的方式 requestLocationUpdates String provider long minTime float minDistance LocationLis
  • 无法理解 Java 地图条目集

    我正在看一个 java 刽子手游戏 https github com leleah EvilHangman blob master EvilHangman java https github com leleah EvilHangman b
  • Clip 在 Java 中播放 WAV 文件时出现严重延迟

    我编写了一段代码来读取 WAV 文件 大小约为 80 mb 并播放该文件 问题是声音播放效果很差 极度滞后 你能告诉我有什么问题吗 这是我的代码 我称之为doPlayJframe 构造函数内的函数 private void doPlay f
  • 归并排序中的递归:两次递归调用

    private void mergesort int low int high line 1 if low lt high line 2 int middle low high 2 line 3 mergesort low middle l
  • 如何手动发送django异常日志?

    我的应用程序中有一个应该返回的特定视图HttpResponse 如果一切都成功完成并且类似HttpResponseBadRequest 否则 此视图适用于外部数据 因此可能会引发一些意外的异常 我当然需要知道发生了什么 所以我有这样的东西
  • 使用 AWS Java SDK 为现有 S3 对象设置 Expires 标头

    我正在更新 Amazon S3 存储桶中的现有对象以设置一些元数据 我想设置 HTTPExpires每个对象的标头以更好地处理 HTTP 1 0 客户端 我们正在使用AWS Java SDK http aws amazon com sdkf
  • 如何在 Maven 中显示消息

    如何在 Maven 中显示消息 在ant中 我们确实有 echo 来显示消息 但是在maven中 我该怎么做呢 您可以使用 antrun 插件
  • Keycloak - 自定义 SPI 未出现在列表中

    我为我的 keycloak 服务器制作了一个自定义 SPI 现在我必须在管理控制台上配置它 我将 SPI 添加为模块 并手动安装 因此我将其放在 module package name main 中 并包含 module xml 我还将其放
  • com.jcraft.jsch.JSchException:身份验证失败

    当我从本地磁盘上传文件到远程服务器时 出现这样的异常 com jcraft jsch JSchException Auth fail at org apache tools ant taskdefs optional ssh Scp exe
  • java8 Collectors.toMap() 限制?

    我正在尝试使用java8Collectors toMap on a Stream of ZipEntry 这可能不是最好的想法 因为在处理过程中可能会发生异常 但我想这应该是可能的 我现在收到一个我不明白的编译错误 我猜是类型推理引擎 这是

随机推荐

  • python 从外部引入变量并运行该程序

    1 python程序部分 import argparse FLAGS tf app flags FLAGS office31 flags train parser argparse ArgumentParser parser add arg
  • java自动化测试框架基础eclipse+maven配置

    java自动化测试框架基础eclipse maven配置 文章目录 java自动化测试框架基础eclipse maven配置 一 maven安装配置 二 eclipse中使用maven 一 maven安装配置 Maven是一个项目构建和管理
  • IDEA安装及配置

    目录 下载与安装 IDEA文件目录介绍 IDEA优化配置 提高启动和运行 下载与安装 IDEA下载网址 JetBrains Essential tools for software developers and teams 在官网中找到自己
  • make: *** No rule to make target 错误原因、分析和解决办法

    问题描述 在用codewarrior编译的时候 遇到编译器报如下错误 mingw32 make No rule to make target D CW Workspace Renalt PBG BOOT Project Settings L
  • 2021常见面试题汇总(持续更新)

    2021常见面试题汇总 1 Valatile的定义和使用 1 1 可见性 1 2 有序性 2 syc1 8之后有什么区别 3 synchronized和Lock的区别 4 redis如何进行大key或value值删除 5 redis如何进行
  • SQL server 查询语句大全

    在 SQL Server 中 查询语句是最常用的语句类型 用于从数据库中提取有用的信息 SQL Server 中常用的查询语句有 SELECT FROM WHERE GROUP BY HAVING 和 ORDER BY 1 SELECT S
  • 【数电】常用时序逻辑电路模块总结

    文章目录 同步置零和异步置零 同步预置数和异步预置数 一 移位寄存器 I D触发器构成的4位移位寄存器 II 双向移位寄存器 74HC194 二 计数器 I 同步计数器 i 同步二进制计数器 1 同步二进制加法计数器 74161 2 同步二
  • iOS中自动消失提示框的实现

    iOS中自动消失提示框的实现 添加一个提示框 UIAlertView alert UIAlertView alloc initWithTitle 提示 message 你很漂亮 delegate self cancelButtonTitle
  • chrome浏览器安装失败,已解决(方便)

    原因分析 如果是第一次安装 一般都会安装成功 倘若报错后安装失败 说明之前电脑上存在Google Chrome 谷歌浏览器 安装的残余 导致再次安装时 无法将安装的数据正常的写入注册表 因为在软件安装过程中 都会将必要的文件添加到注册表中
  • 大数据学习之Scala——02Scala基础

    一 杂项 1 Scala语言输出的三种方式 字符串通过 号连接 类似java printf用法 类似C语言 字符串通过 传值 格式化输出 字符串插值 通过 引用 类似PHP println name name age age url url
  • dosbox中out of memory_在Rust中实现goto逻辑

    众所周知 在Rust中是没有goto表达式的 最近在 试着用Rust练习翻新一些古代陈旧代码 结果这堆古代的pascal代码中就有很多goto语句 于是写了几个宏来模拟了一下 在这里也写一篇文章介绍一下 希望给大家在思路上有所帮助 如果不想
  • ​​PMP项目管理—第3章 项目经理的角色。

    PMBOK项目管理知识体系指南 PMP项目管理学习笔记 总 第1章 引论 第2章 项目运行环境 第3章 项目经理的角色 第4章 项目整合管理 第5章 项目范围管理 第6章 项目进度管理 第7章 项目成本管理 第8章 项目质量管理 第9章 项
  • c/c++入门教程 - 1.基础c/c++ - 1.0 Visual Studio 2019安装环境搭建

    推荐视频课程 https www bilibili com video BV1et411b73Z p 2 已投币三连 b站果然是个学习的网站 本来是想在linux环境下运行QT 于是先学了几个月linux嵌入式驱动开发 后来发现太底层了 与
  • 【FPGA】面试问题及答案整理合集

    面试问题及答案整理合集 1 硬件描述语言和软件编程语言的区别 2 FPGA选型问题 3 建立时间和保持时间问题 3 亚稳态问题 4 竞争和冒险问题 5 乒乓操作问题 6 同步和异步逻辑电路 7 同步复位和异步复位 8 MOORE 与 MEE
  • CUDA异步并发之CUDA流详解

    CUDA中得异步并发 CUDA 将以下操作公开为可以彼此同时操作的独立任务 在主机上计算 设备上的计算 从主机到设备的内存传输 从设备到主机的内存传输 在给定设备的内存中进行内存传输 设备之间的内存传输 这些操作之间实现的并发级别将取决于设
  • 《Transfer Adaptation Learning: A Decade Survey》阅读笔记

    摘要 传统机器学习的目的是通过最小化训练数据的正则化经验风险 对测试数据的最小期望风险最小的模型 但假设训练数据和测试数据具有相似的联合概率分布 TAL的目标是通过从语义相关但分布不同的源域学习知识 来建立能够执行目标域任务的模型 在经典的
  • MySQL必知必会——第四章检索数据

    检索数据 本章将介绍如何使用SELECT语句从表中检索一个或多个数据列 SELECT语句 SQL语句是由简单的英语单词关键字构成的 每个SQL语句都由一个或多个关键字构成 最常用的SQL语句就是SELECT语句 它的用途是从一个或多个表中检
  • cycleGan的算法流程实现

    关于cycleGan的算法流程实现 只是看代码后进行了总结 具体细节可以自行寻找代码查看
  • mybatis逆向工程详细配置讲解(全)

    目录 前言 1 配置文件 2 GeneratorMapper xml 3 启动配置 4 生成文件讲解 5 细节 前言 使用mybatis提供的逆向工程生成实体bean 映射文件 Dao接口 而不用人为的去书写代码 显得比较麻烦 具体代码模块
  • ThreadPoolExecutor是如何处理任务的异常情况

    本文因生产环境线程池某些场景下的任务异常后 日志文件中没有被记录进来产生的困惑引发的思考 当然如果所有异步的业务方法run里面都加上一层try catch 就可以主动捕获所有的异常 也能够记录到日志文件中 然而总有一些人总有一些时候不小心漏