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

Python爬虫实战:从巨潮网批量抓取上市公司年报JSON数据 网络爬虫作为自动化数据采集的核心技术,其原理是通过模拟浏览器行为向服务器发送HTTP请求并解析响应,从而高效获取结构化信息。在金融数据分析领域,这项技术价值尤为突出,能够将研究人员从繁琐的手工数据收集中解放出来,聚焦于更高阶的模型构建与价值挖掘。典型的应用场景包括上市公司财务研究、市场趋势分析和风险预警系统开发。本文以Python的requests库为核心工具,详细解析如何通过分析巨潮资讯网的JSON数据接口,构建一个稳定、高效的上市公司年报数据采集管道,并重点探讨了接口逆向、分页爬取、PDF文件下载等关键 阅读详情

本文因生产环境线程池某些场景下的任务异常后,日志文件中没有被记录进来产生的困惑引发的思考。
当然如果所有异步的业务方法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

从 CDS 接口到 ABAP 运行时,深入理解 SAP RAP Custom Entity CDS Custom Entity 是一种非 SQL、瞬态的 CDS 实体,不依赖 HANA SQL 读取数据,而是通过 ABAP 类实现查询逻辑(如 RFC、OData、BAPI 等)。它在 CDS 层定义数据契约(字段、键、关联、注解),由 @ObjectModel.query.implementedBy 指向实现 IF_RAP_QUERY_PROVIDER~SELECT 的类,真正执行数据获取。适用于无法用 SQL 表达的数据源,如远程系统或 API。其核心是“接口 + 实现”模式,运行时由 RAP 阅读详情

相关推荐

android OTA升级之后,apk崩溃无法启动

也就是说,我们这个高低版本是fingerprint相同的情况,这个问题追述到jenkins编译,启动job时会初始化一个BUILD_NUMBER值,这个值正好跟Android的build系统的名称重复,这就导致Android build系统那一套拼接时间戳的值不会走,因为jenkins一启动就会定义该值,而我们jenkins服务器迁移过,这个值又会从1开始累计,导致前后版本都是16,从而导致fingerprint一致。问题背景:系统版本从低版本升级到高版本后,apk崩溃启动失败。硬件平台:QCS6125。

kevin's cache的专栏 1347

项目问题:线程池使用FutureTask得不到抛出的错误

线程池使用FutureTask得不到抛出的错误 一、使用Thread public static void main(String[] args) { Test test = new Test(); FutureTask futureTask = new FutureTask<>(test); Thread thread = new Thread(futureTask); thread.start(); try {

蒋蒋 1598

从CPU视角看PCIe:深入理解x86/ARM平台上BAR、MMIO和PIO的地址翻译与访问机制

本文深入探讨了CPU如何通过BAR、MMIO和PIO机制访问PCIe设备的配置空间,详细解析了x86和ARM平台在地址翻译与访问路径上的差异。重点分析了PCIe配置空间中BAR寄存器的作用与探测机制,以及操作系统如何管理PCIe资源,为开发者提供了性能优化与调试的实用技巧。

weixin_30175731的博客 324

ThreadpoolExecutor处理task执行异常

日常开发中使用线程池时,发现线程池里的任务异常中断/失败是不会打印错误信息的,导致结果异常还不知道发生什么。线程池提交作业后,worker不会主动抛出异常。需要通过future对象的。任务提交完后,得到future对象列表/字典。是否为空判断该任务是否有异常信息。遍历线程池中完成的task。线程的执行结果是否异常。遍历完成的task也就是。遍历完成的task也就是。方法提交任务后后返回。

weixin_45214524的博客 1384

第 4 课 树莓派5的换源与中文输入法

树莓派换源、中文输入法的安装

嵌入式老牛的博客 2288

AI时代,程序员何去何从?别慌,看完这篇你就明白了

AI时代程序员的发展方向 摘要:随着AI代码助手的普及,程序员行业面临变革。本文通过实际案例展示AI写代码的能力与局限:AI能生成基础功能代码(如RESTful API、数据处理脚本),但缺乏业务理解、系统设计和复杂逻辑处理能力。程序员的核心价值在于需求分析、架构设计、性能优化等高层能力。未来程序员应: 掌握AI工具提升效率 专注于创新和架构设计 培养业务理解能力 持续学习新技术 AI不会取代程序员,但会淘汰不会使用AI的程序员。建议开发者拥抱AI工具,将其作为助手而非威胁,聚焦高价值工作。

yunque_xianguang的博客 908

java线程池ThreadPool处理线程异常

线程池线程异常处理

weixin_43866043的博客 3311

svn co --depth和svn up --set-depth 只check out下一层文件 只更新指定文件 只更新下一层的用法

svn的工程可能很大 有时我们只需要部分文件即可 这里介绍几个命令 1.svn co(check out) --depth --depth=empty,files,immediates,infinity 假设某svn结构如下 svn://root/PRO/A_dir/a svn://root/PRO/B_dir/b svn://root/PRO/c_file svn co --depth=emptysvn://root/PRO 此时只会在本地check out出 PRO/的空目录。 sv.

cy413026的博客 1万+

ThreadPoolExecutor获取原始异常

FutureTask的主要作用是在多线程环境下,获取异步执行的结果。在执行该任务时,可以通过Future接口的get()方法来获取任务的执行结果,而不必串行阻塞等待。由此,FutureTask可以通过异步的方式充分利用服务器资源,提高代码的执行效率与响应速度。ThreadPoolTaskExecutor是Spring框架提供的一个线程池实现,它是基于Java的ThreadPoolExecutor实现的。ThreadPoolTaskExecutor可以管理线程池中的线程,以满足多线程并发执行任务的需要。

FirstMrRight的博客 1464

GD32F470移植LWIP协议,PHY芯片DP83848I

也就是说前24位是有规定的,其中48位MAC中的前几位也是有着对应的含义的。2、旧版LWIP(如1.4.1)存在DHCP状态机卡死问题,建议升级至2.1.3+。

m0_61973119的博客 1591

在 SAP 中实现“租赁资产”管理,核心是把 IFRS16/ASC842 等新租赁准则的要求落地到系统

摘要:本文详细介绍了在SAP S/4HANA系统中实现IFRS16/ASC842租赁准则的具体操作流程。主要内容包括:1) 系统前置配置,如激活租赁会计组件、定义租赁类型和会计科目;2) 租赁合同创建与主数据维护;3) 初始确认的会计处理;4) 日常折旧计提和租金支付流程;5) 合同变更与终止管理。通过RE-FX-LM模块与固定资产模块的集成,系统可自动生成使用权资产和租赁负债,支持从合同创建到报表披露的全生命周期管理,确保符合新租赁准则要求。

金牌架构师 987

关于多线程中抛异常的这个面试题我再说最后一次!

这里why的第 66 篇原创文章 一道面试题 我一年前写过这篇文章《有的线程它死了,于是它变成一道面试题》,这是早期作品,遣词造句,排版行文都有一点稚嫩,但是不知咋地,还是有很多人看过。 甚至已经进入了某网红公司的面试题库里面。 所以我后面应该会重写一下,翻新翻新,再补充一点新的东西进去。 现在先回顾一下这篇文章抛出的问题和问题的答案: 一个线程池中的线程异常了,那么线程池会怎么处理这个线程? 这个题是我遇到的一个真实的面试题,当时并没有回答的很好。然后通过上面的文章,我在源码中寻找到了答案。 先给大家

qq_27243343的博客 1637

DataCastle-租金预测

ID:编号;时间:房屋信息采集的时间;小区名:房屋所在小区,已脱敏处理;小区房屋出租数量:小区出租房屋数量,已脱敏处理;楼层:0、1、2分别表示楼层低,中,高;总层数:房屋所在建筑的总楼层数,已脱敏处理;房屋面积:房屋面积数值,已脱敏处理;房屋朝向:房屋的朝向;居住状态:房屋的居住状态,表示是否已出租或居住中,已脱敏处理;卧室数量:户型信息,数字表示卧室的个数;卫的数量:户型信息,数字表示卫生间的个数;厅的数量:户型信息,数字表示厅的个数;

优快云 精品推荐 3457

ThreadPoolExecutor线程池中的线程发生异常时

结论:发生异常的线程被回收,重新填充一个新的线程 证明: ExecutorService pool = Executors.newFixedThreadPool(2); pool.execute(() -> { try { Thread.sleep(2000); System.out.println(Thread.currentThread().getId()+...

m0_37133375的博客 3552

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

【编程底层思考】详述ThreadPoolExecutor线程池的所有参数及其含义,以及线程池在处理任务时的完整处理过程

在Java中,java.util.concurrent包提供了一套线程池执行器(Executor)的丰富实现,其中ThreadPoolExecutor是线程池的具体实现类。Executors类则是一个工厂类,用于方便地创建不同类型的线程池。

Dylaniou的博客 1703

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

ThreadPoolExecutor——高效处理并发任务的必备良器

ThreadPoolExecutor是Java中用于管理线程池的一个类,它能够创建和管理线程池,以提高应用程序的性能和可靠性。它具有灵活的线程池管理、高效的任务处理和可靠的异常处理等特点。尤其适用于任务量大、执行时间长、任务类型多样的应用场景,例如Web服务器、数据库连接池、文件处理等。通过合理地设置线程池参数、处理任务和异常、监控和调优线程池,可以提高应用程序的性能和可靠性,避免任务丢失或线程池崩溃的问题。

xindoo 3611

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

ThreadPoolExecutor参数说明

一、ThreadPoolExecutor的重要参数 1、corePoolSize:核心线程数 * 核心线程会一直存活,及时没有任务需要执行 * 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理 * 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭 2、queueCapacity:任务队列容量(阻塞队列) * 当核心线程数达到最大时,新任务会放在队列中排

sinat_15946141的博客 3万+

面试|ThreadPoolExecutor中的线程如何从等待队列获取任务

1.问题引出 我们知道一个线程创建的时候就会附带一个Runnable任务,如果该Runable任务执行完毕后,该线程如何从线程池的等待队列中获取一个任务呢? 上一篇博客讲到线程池中线程和该线程的第一个任务封装在一个Worker类中,这个Worker类本身实现了Runnable接口,源码定义: private final class Worker extends AbstractQueuedSync...

星空下的程序猿 7264

ThreadPoolExecutor任务提交

execute()是 java.util.concurrent.Executor接口中唯一的方法,JDK注释中的描述是“在未来的某一时刻执行命令command”,即向线程池中提交任务,在未来某个时刻执行,提交的任务必须实现Runnable接口,该提交方式不能获取返回值。下面是对execute()方法内部原理的分析,分析前先简单介绍线程池有哪些状态,在一系列执行过程中涉及线程池状态相关的判断。以下分析基于JDK 1.7 线程池的执行流程 1、如果线程池中的线程数量少于corePoolSize,就创建新的线程

qq_52041753的博客 661

ThreadPoolExecutor任务提交与停止流程及底层实现

ThreadPoolExecutor底层源码剖析,执行过程、停止过程中所涉及的函数执行过程,如excutor、线程池、Worker、task、shutdown等

如何心安理得的在老板眼皮下摸鱼 1万+

ForkJoinPool和ThreadPoolExecutor区别

ForkJoinPool和ThreadPoolExecutor的主要区别体现在任务执行的方式和适用的场景上。

刘皇叔说Java的博客 2144

Python ThreadPoolExecutor 任务队列阻塞提交

分享一个ThreadPoolExecutor中阻塞提交任务队列的小例子,保证充分利用线程资源的同时防止队列中任务过多导致内存占用过大。

Keithen的摸鱼记录 3707

在 FastAPI 中高效处理阻塞任务:利用 ThreadPoolExecutor 实现异步非阻塞操作

通过使用,我们可以在 FastAPI 应用中有效地处理阻塞任务,确保应用的响应性和并发处理能力。这种方法不仅提高了应用的性能,还使得代码更加清晰和易于维护。Uvicorn 是一个超快的 ASGI 服务器,专门用来运行像FastAPI或Starlette这样的异步 Python Web 框架。ASGI= 异步 Web 服务器接口(类似 WSGI,但支持 async)Uvicorn 基于uvloop(比标准 asyncio 快很多)和httptools它能高效地处理成千上万的并发连接。

Whiser的博客 983

ThreadPoolExecutor异常处理

java.util.concurrent包中的ThreadPoolExecutor,提供了java语言的线程池,你可以提交一个返回结果的任务(submit(Callable),返回Future),或者执行一个不返回结果的任务(execute(Runnable)),但提交的任务可能会抛异常,这就需要处理异常: 1. 对于submit的任务,框架会将异常保持在future里,并包装在Execut...

iteye_6700的博客 691

揭秘ThreadPoolExecutor回调函数:如何高效处理异步任务结果与异常

掌握Python ThreadPoolExecutor 的回调,高效处理异步任务结果与异常。适用于IO密集型任务,通过add_done_callback方法及时获取执行状态,提升程序响应速度与稳定性,异常处理更灵活,值得收藏。

BytePulse的博客 890

ThreadPoolExecutor队列满时提交任务阻塞

项目中有一个需求:给线程池提交任务的时候,如果任务队列已满,需要ThreadPoolExecutor.execute调用阻塞等待。google了相关的资料,记录在这里,供有同样需求的同行参考。 https://stackoverflow.com/questions/4521983/java-executorservice-that-blocks-on-submission-after-a-cert...

韦远科的专栏 5693
上一篇: 如何成为github开源项目的贡献者contributor
下一篇: Git基础操作:push提交多个文件成功后如何撤销回退某个文件,回退代码到某次commit
明月(Alioo)
博客等级 码龄18年 267粉丝 207原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明月(Alioo)

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值