这篇文章主要介绍“怎么解决异步任务所导致的问题”,在日常操作中,相信很多人在怎么解决异步任务所导致的问题问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么解决异步任务所导致的问题”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都做网站、成都网站建设、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的秀英网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
前言
先简单说下这段代码,就是使用一个异步线程执行一段业务逻辑,示例代码如下:
// 前置逻辑 ..... Thread thread=new Thread(new Runnable() { @Override public void run() { try { // 异步线程执行其他业务逻辑 } catch (Exception e) { // 不进行任何代码处理 } } }); thread.start();
凭着老程序员的经验,猜到可能是异步线程内发生了异常,导致异步线程退出,不再继续执行。而又因为上述代码「吃掉」了异常,这就导致我们从外部看起来这个工程跑着跑着就不动了,日志什么也没了。
于是改造了一下,打印出相关异常日志,最终定位问题,原来是小姐姐造的数据存在问题,从而引发 NPE 问题。
「不知道大家有没有碰到过上面的情况,使用线程异步执行相关逻辑,但是执行到一半突然就像卡主一般,不再继续往下执行。」
总结起来分为下面三种情况:
异步任务长时间被阻塞
异步任务发生异常
异步任务异常被吃掉
异步任务长时间被阻塞
第一种,异步线程执行任务,这个任务需要通过网络调用其他远端服务。假设服务端响应的非常慢,而我们设置的网络超时时间又很长,这就会导致这个线程长时间被阻塞。
假设异步任务伪码如下:
ThreadPoolExecutor threadPool= ....; threadPool.execute(() -> { // 1.调用远端服务 Socket socket....; // 2.设置超时时间 socket.setSoTimeout(60*1000); // 3.读取服务端返回 socket.read(); });
上面程序中,如果服务端一直没有返回,那么异步线程将会一直被阻塞,直到超时。
这种情况其实还好,我们无非等待一段时间,就可以看到异步线程继续往下执行任务。
举一个极端的例子,假设上面的代码没有设置超时时间,而服务端一直没有返回响应,「此时异步线程就会被一直阻塞」。
除了上面网络读取阻塞的例子,常见情况还有
执行了长时间休眠,比如 TimeUnit.MINUTES.sleep(60)
内部发生了死锁
等等
如果异步线程长时间被阻塞,而异步任务执行又比较频繁,那么线程池内可用线程将会被慢慢耗尽,此时后续任务就会被拒绝执行。
解决办法
其实非常简单,首先我们使用 jstack 命令 「dump」 一下当前 Java 应用的线程堆栈情况,然后根据线程池名字定位相关线程即可。
网上随便找了堆栈图
如果没有自定义线程池 ThreadFactory 参数,那查找定位被阻塞线程就比较麻烦了。
所以创建线程池建议自定义 ThreadFactory 参数,这对于后期排查问题非常有用。
异步任务异常未捕获
上面的情况,异步线程其实还活着,只是被阻塞没办法执行后续的逻辑。
那这一类情况呢,与上面不太一样,由于异步任务内部发生错误,抛出异常,而代码逻辑中又没有进行捕获处理,从而导致线程提前异常退出。
异常退出伪码如下:
// 1.创建执行的任务 Runnable runnable=new Runnable() { @Override public void run() { // 执行前置逻辑 // 抛出异常 int i=100/0; // 执行后置逻辑 } }; // 2.创建线程 Thread thread=new Thread(runnable); // 3.运行异步线程 thread.start(); // 其他业务逻辑
上述代码中,异步线程执行到除零逻辑,将会抛出异常,然后异步线程将会异常退出。
「异步线程内抛出的异常日志仅仅只会被打印到控制台,而不会被记录到日志文件中。」
所以正常的业务日志中是见不到线程异常的日志,这就给了我们一种假象,异步线程看起来还在执行任务,其实它已经挂了。
PS:上面的话可能不好理解,举个例子,如果你使用 IDEA 执行上面这段程序,异常日志将会被输出到 IDEA 下方控制台。
而如果我们在 Linux 机器上执行这段程序,异常日志仅仅只会显示在当前终端窗口上,一旦关闭当前终端窗口,日志就没。了。
如果想要保存这种日志,我们需要将 stdout 重定向到日志文件中,比如执行以下命令:
-- 将 stdout 重定向输出到文件中 nohup java xxxx > $STDOUT_FILE 2>&1 &
解决办法
第一种解决办法,其实很多读者已经想到了,异步线程内使用 try..catch 语句捕获所有异常即可。
「没错,就是这么简单。」
不过这里提一点,一般我们使用 try..catch仅仅只会捕获 Exception异常。
那么极端情况下,异步线程内如果抛出 Error,比如抛出了 java.lang.NoClassDefFoundError,此时是没法捕获,异步线程依旧会异常退出。
所以我们可以使用try..catch捕获 Throwable,这样及时发生 Error错误,也会被捕获。
不过个人觉得捕获Exception异常就够了,正常工程应用很少会发生 Error错误,所以我们只要了解有这个可能即可。
ps:之前同事上线一个应用,使用异步线程执行任务,每次执行到一半,都不再继续执行。
由于异步线程内使用try..catch捕获处理了 Exception异常,所以找了半天不知道什么问题。
最后,小黑哥排查 stdout 输出日志,才发现异步线程发生 Error错误。
这种解决本法需要我们主动去捕获异常,而下面第二种解决办法,设置线程异常处理方法。
一旦设置完成,如果异步线程内发生异常,线程退出之前将会调用异常处理方法。
我们拿 Thread 来讲,其设置方法如下:
Runnable runnable=new Runnable() { @Override public void run() { int i=100/0; } }; Thread thread=new Thread(runnable); thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println(t.getName()+"发生异常"+e.getMessage()); } }); thread.start();
不过生产环境不建议直接使用 Thread,我们需要使用线程池代替。
线程池设置异常处理方法可以分为两种,如果我们使用 ThreadPoolExecutor#execute执行异步任务,那我们需要在自定义线程池的时候,使用 ThreadFactory 设置。
ThreadPoolExecutor threadPool =new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100), // 这里使用 Guava 的 ThreadFactoryBuilder 类,方便构造 ThreadFactory new ThreadFactoryBuilder().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { // 处理异常 } }).build() );
如果你当前使用 ThreadPoolExecutor#submit执行异步任务,那就简单了,我们可以直接通过 Future#get获取到线程内抛出的异常。
Future> future = threadPool.submit(new Callable
异步任务异常被吃掉
好了,终于到最后一种情况了,小黑哥这次碰到就是这种??。
这种情况具体来说就是异步线程内使用 try..catch 语句捕获了所有异常,但是没有在 catch语句中进行任何代码处理。
Thread thread=new Thread(new Runnable() { @Override public void run() { try { int i=100/0; } catch (Exception e) { // 不进行任何代码处理 } } }); thread.start();
如上述代码所示,catch语句中没有进行任何代码处理。即使异步线程内真发生了异常,也不会有任何提示,这个异常就像被吃掉一般。
到此,关于“怎么解决异步任务所导致的问题”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!
当前题目:怎么解决异步任务所导致的问题
本文网址:http://cqwzjz.cn/article/pghpcs.html