Java线程池执行的任务抛出异常看不到日志

问题描述: 

最近项目中一些异步执行的逻辑没有运行异常却没有打出日志 给定位问题带来麻烦??

问题分析

接下来我们来看一下java中的线程池是如何运行我们提交的任务的,详细流程比较复杂,这里我们不关注,我们只关注任务执行的部分。java中的线程池用的是ThreadPoolExecutor,真正执行代码的部分是runWorker方法:final void runWorker(Worker w)

  
  
  1. //省略无关部分
  2. try {
  3. beforeExecute(wt, task);
  4. Throwable thrown = null;
  5. try {
  6. task.run(); //执行程序逻辑
  7. } catch (RuntimeException x) { //捕获RuntimeException
  8. thrown = x; throw x;
  9. } catch (Error x) { //捕获Error
  10. thrown = x; throw x;
  11. } catch (Throwable x) { //捕获Throwable
  12. thrown = x; throw new Error(x);
  13. } finally {
  14. afterExecute(task, thrown); //运行完成,进行后续处理
  15. }
  16. } finally {
  17. task = null;
  18. w.completedTasks++;
  19. w.unlock();
  20. }
  21. //省略无关部分

可以看到,程序会捕获包括Error在内的所有异常,并且在程序最后,将出现过的异常和当前任务传递给afterExecute方法。

而ThreadPoolExecutor中的afterExecute方法是没有任何实现的:

  
  
  1. protected void afterExecute(Runnable r, Throwable t) { }

也就是说,默认情况下,线程池会捕获任务抛出的所有异常,但是不做任何处理。

存在问题

想象下ThreadPoolExecutor这种处理方式会有什么问题? 
这样做能够保证我们提交的任务抛出了异常不会影响其他任务的执行,同时也不会对用来执行该任务的线程产生任何影响。 
问题就在afterExecute方法上,这个方法没有做任何处理,所以如果我们的任务抛出了异常,我们也无法立刻感知到。即使感知到了,也无法查看异常信息。

所以,作为一名好的开发者,是不应该允许这种情况出现的。

如何避免这种问题

思路很简单。 
1. 在提交的任务中将异常捕获并处理,不抛给线程池。 
2. 异常抛给线程池,但是我们要及时处理抛出的异常。

第一种思路很简单,就是我们提交任务的时候,将所有可能的异常都Catch住,并且自己处理,任务的大致代码如下:

  
  
  1. @Override
  2. public void run() {
  3. try {
  4. //处理所有的业务逻辑
  5. } catch (Throwable e) {
  6. //打印日志等
  7. } finally {
  8. //其他处理
  9. }
  10. }

说白了就是把业务逻辑都trycatch起来。 
但是这种思路的缺点就是:1)所有的不同任务类型都要trycatch,增加了代码量。2)不存在checkedexception的地方也需要都trycatch起来,代码丑陋。

第二种思路就可以避免上面的两个问题。 
第二种思路又有以下几种实现方式: 
1. 自定义线程池,继承ThreadPoolExecutor并复写其afterExecute(Runnable r, Throwable t)方法。 
2. 实现Thread.UncaughtExceptionHandler接口,实现void uncaughtException(Thread t, Throwable e);方法,并将该handler传递给线程池的ThreadFactory 
3. 采用Future模式,将返回结果以及异常放到Future中,在Future中处理 
4. 继承ThreadGroup,覆盖其uncaughtException方法。(与第二种方式类似,因为ThreadGroup类本身就实现了Thread.UncaughtExceptionHandler接口)

下面是以上几种方式的代码

方式1

自定义线程池:

  
  
  1. final class PoolService {
  2. // The values have been hard-coded for brevity
  3. ExecutorService pool = new CustomThreadPoolExecutor(
  4. 10, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10));
  5. // ...
  6. }
  7. class CustomThreadPoolExecutor extends ThreadPoolExecutor {
  8. // ... Constructor ...
  9. public CustomThreadPoolExecutor(
  10. int corePoolSize, int maximumPoolSize, long keepAliveTime,
  11. TimeUnit unit, BlockingQueue<Runnable> workQueue) {
  12. super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
  13. }
  14. @Override
  15. public void afterExecute(Runnable r, Throwable t) {
  16. super.afterExecute(r, t);
  17. if (t != null) {
  18. // Exception occurred, forward to handler
  19. }
  20. // ... Perform task-specific cleanup actions
  21. }
  22. @Override
  23. public void terminated() {
  24. super.terminated();
  25. // ... Perform final clean-up actions
  26. }
  27. }

方式2

实现Thread.UncaughtExceptionHandler接口,实现void uncaughtException(Thread t, Throwable e);方法,并将该handler传递给线程池的ThreadFactory

  
  
  1. final class PoolService {
  2. private static final ThreadFactory factory =
  3. new ExceptionThreadFactory(new MyExceptionHandler());
  4. private static final ExecutorService pool =
  5. Executors.newFixedThreadPool(10, factory);
  6. public void doSomething() {
  7. pool.execute(new Task()); // Task is a runnable class
  8. }
  9. public static class ExceptionThreadFactory implements ThreadFactory {
  10. private static final ThreadFactory defaultFactory =
  11. Executors.defaultThreadFactory();
  12. private final Thread.UncaughtExceptionHandler handler;
  13. public ExceptionThreadFactory(
  14. Thread.UncaughtExceptionHandler handler)
  15. {
  16. this.handler = handler;
  17. }
  18. @Override public Thread newThread(Runnable run) {
  19. Thread thread = defaultFactory.newThread(run);
  20. thread.setUncaughtExceptionHandler(handler);
  21. return thread;
  22. }
  23. }
  24. public static class MyExceptionHandler extends ExceptionReporter
  25. implements Thread.UncaughtExceptionHandler {
  26. // ...
  27. @Override public void uncaughtException(Thread thread, Throwable t) {
  28. // Recovery or logging code
  29. }
  30. }
  31. }

方式3

继承ThreadGroup,覆盖其uncaughtException方法

  
  
  1. public class ThreadGroupExample {
  2. public static class MyThreadGroup extends ThreadGroup {
  3. public MyThreadGroup(String s) {
  4. super(s);
  5. }
  6. public void uncaughtException(Thread thread, Throwable throwable) {
  7. System.out.println("Thread " + thread.getName()
  8. + " died, exception was: ");
  9. throwable.printStackTrace();
  10. }
  11. }
  12. public static ThreadGroup workerThreads =
  13. new MyThreadGroup("Worker Threads");
  14. public static class WorkerThread extends Thread {
  15. public WorkerThread(String s) {
  16. super(workerThreads, s);
  17. }
  18. public void run() {
  19. throw new RuntimeException();
  20. }
  21. }
  22. public static void main(String[] args) {
  23. Thread t = new WorkerThread("Worker Thread");
  24. t.start();
  25. }
  26. }

确实这种方式与上面通过ThreadFactory来指定UncaughtExceptionHandler是一样的,只是代码逻辑不同,但原理上都是一样的,即给线程池中的每个线程都指定一个UncaughtExceptionHandler。

** 注意:上面三种方式针对的都是通过execute(xx)的方式提交任务,如果你提交任务用的是submit()方法,那么上面的三种方式都将不起作用,而应该使用下面的方式 **

方式4

如果提交任务的时候使用的方法是submit,那么该方法将返回一个Future对象,所有的异常以及处理结果都可以通过future对象获取。 
采用Future模式,将返回结果以及异常放到Future中,在Future中处理

  
  
  1. final class PoolService {
  2. private final ExecutorService pool = Executors.newFixedThreadPool(10);
  3. public void doSomething() {
  4. Future<?> future = pool.submit(new Task());
  5. // ...
  6. try {
  7. future.get();
  8. } catch (InterruptedException e) {
  9. Thread.currentThread().interrupt(); // Reset interrupted status
  10. } catch (ExecutionException e) {
  11. Throwable exception = e.getCause();
  12. // Forward to exception reporter
  13. }
  14. }
  15. }

总结

  1. java线程池会捕获任务抛出的异常和错误,但不做任何处理
  2. 好的程序设计应该考虑到对于类异常的处理
  3. 处理线程池中的异常有两种思路: 
    1)提交到线程池中的任务自己捕获异常并处理,不抛给线程池 
    2)由线程池统一处理
  4. 对于execute方法提交的线程,有两种处理方式 
    1)自定义线程池并实现afterExecute方法 
    2)给线程池中的每个线程指定一个UncaughtExceptionHandler,由handler来统一处理异常。
  5. 对于submit方法提交的任务,异常处理是通过返回的Future对象进行的。

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以使用Java的Executor框架来实现线程池执行任务,同时结合日志框架和重试机制来实现任务失败重试和任务日志。以下是一个简单的示例: ```java import java.util.concurrent.*; public class TaskExecutor { private static final int MAX_THREADS = 10; private static final int MAX_RETRY_COUNT = 3; private static final int RETRY_DELAY_SECONDS = 5; private ExecutorService executor; public TaskExecutor() { executor = Executors.newFixedThreadPool(MAX_THREADS); } public void execute(Runnable task) { executor.execute(() -> { int retryCount = 0; boolean success = false; while (!success && retryCount < MAX_RETRY_COUNT) { try { task.run(); success = true; } catch (Exception e) { retryCount++; System.out.println("Task execution failed, retrying in " + RETRY_DELAY_SECONDS + " seconds..."); try { Thread.sleep(RETRY_DELAY_SECONDS * 1000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } if (!success) { System.out.println("Task execution failed after " + MAX_RETRY_COUNT + " retries."); } // Log task execution result if (success) { System.out.println("Task executed successfully."); } else { System.out.println("Task execution failed."); } }); } public void shutdown() { executor.shutdown(); } } ``` 在这个示例中,我们创建了一个名为TaskExecutor的类,它使用Java的Executor框架来实现线程池执行任务。execute()方法接受一个Runnable对象,该对象实现了要执行任务逻辑。在任务执行期间,我们使用一个while循环来实现失败重试,最多重试MAX_RETRY_COUNT次。如果任务成功执行,则输出"Task executed successfully.",否则输出"Task execution failed."。在任务执行期间,我们使用System.out.println()语句来输出任务执行日志。当然,你可以使用更高级的日志框架来记录任务日志。 最后,我们在TaskExecutor类中实现了一个shutdown()方法,该方法用于关闭线程池

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值