一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考
2018年12月12日18:44:53
一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考
案件现场
不久前,在开发改造公司一个端到端监控日志系统的时候,出现了一个bug:有个扫表写日志的线程无故挂掉。
顺藤摸瓜
我看了很久的代码,都没有想出来有什么地方有逻辑问题。万金油的方法是,重启。当我满心欢喜地认为重启是个好方法的时候,问题又重现了。
我有点无奈地看着自己的代码
本我:堪称完美的逻辑,还有什么地方是我没有注意到的吗?
真我:当然有了,你这个菜鸟,你不知道的地方多着呢。
于是,去找老大问一下问题怎么解决,老大说去生产数据库上导十万数据到测试库,然后在本地debug一下。接着,我就从数据库里面导出一万数据开始测试,在eclipse启动进程,日志写在本地文件。很快,问题再一次出现。然后断点,然后找到出问题的地方。出问题的地方如下:
代码就一行:
String timeStamp = DateUtil.str2Date(receiveTime, DateUtil.YYYYMMDDHH24MISS).getTime() + "000";
这行代码的意思是,将字符串的接收时间receiveTime格式化,getTime()得到时间戳,因为格式是要微秒,加了三个零。
DateUtil.str2Date方法:(String时间转化为Date类型,关于时间转换可以看看本人的String、Date和Timestamp的互转)
public static Date str2Date(String dateStr, String dateFormat){
if (StringHelper.isEmpty(dateStr)) {
return null;
}
SimpleDateFormat df = new SimpleDateFormat(dateFormat);
try {
return df.parse(dateStr);
} catch (Exception ex) {
return null;
}
}
这个工具类当parse方法抛出异常的时候返回null,看起来是没有问题的,但是我在转换之后没有判断是否为空即null
,然后就变成了null.getTime()
,接着就抛了一个很常见的NullPointerException
异常。
到这里,看似问题已经解决了,但是问题并没有那么简单。
寻根问底
上面说到的在线程中抛出了NullPointerException
异常,解决方法是增加一个判断是否为空的条件就可以了。但是一般来说,有异常的时候,程序没有捕获异常,日志里或者debug时控制台会打印异常信息,类似这种:
at com.netease.backend.rds.task.CleanHandleThread.run(CleanHandleThread.java:65)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:204)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:662)
但实际上我debug的时候,并没有看到打印的异常信息。我是断点到这一步,发现下一行代码没有执行,我就断定问题是在这里,而且空指针异常一下子就能看出来了。问题来了,为什么没有打印异常信息呢?我想应该是线程的问题,代码里启动这个写日志的定时任务用的是ScheduledExecutorService:
我Google了一下,发现其实有很多前辈都曾遇到过这个问题。
在这些文章中,我找到了我要的答案。我引用其中的一篇文章从一个java线程挂掉的例子讨论分析定位问题基本原则文字作为答案吧。
那么,Java线程挂掉的主要原因是:Any thrown exception or error reaching the executor causes the executor(ScheduledExecutorService) to halt. No more invocations on the Runnable, no more work done. This work stoppage happens silently, you’ll not be informed.
也就是说,如果使用者抛出异常,ScheduledExecutorService 将会停止线程的运行,而且不会报错,没有任何提示信息。
这就是在日志中和控制台都没有看到打印异常信息的原因。
解决方法
写了一个测试类,有兴趣可以研究一下这个bug。
public class ScheduledExecutorServiceThrowExceptionTest {
private static int i = 0;
public static void main(String[] args) {
ScheduledExecutorService exc = Executors.newSingleThreadScheduledExecutor();
exc.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
i++;
if (i==6) {
throw new RuntimeException();
} else {
System.out.println(i);
}
}
}, 0, 1, TimeUnit.SECONDS);
}
}
测试结果是:
结果显示,当程序抛出异常的时候,线程就不再运行了,也就是挂了。
解决方法:
- 1、直接加一个
try-catch
进行异常捕获,然后你可以打印你需要的异常信息或者处理异常。
public class ScheduledExecutorServiceThrowExceptionTest1 {
private static int i = 0;
public static void main(String[] args) {
ScheduledExecutorService exc = Executors.newSingleThreadScheduledExecutor();
exc.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
try {
// doSomething();// TODO:具体业务逻辑
i++;
if (i==6) {
throw new RuntimeException();
} else {
System.out.print(i + " ");
}
} catch (Exception ex) {
System.out.println();
System.out.println("在ScheduledExecutorService中有异常抛出,异常堆栈:" + ex.getStackTrace());
}
}
}, 0, 1, TimeUnit.SECONDS);
}
}
结果是打印了异常信息,且线程没有被中断。
1 2 3 4 5
在ScheduledExecutorService中有异常抛出,异常堆栈:[Ljava.lang.StackTraceElement;@1bb53ed8
7 8 9 10 11 12 13
- 2、通过ScheduledFuture对象返回异常信息
public class ScheduledExecutorServiceThrowExceptionTest2 {
private static int i = 0;
public static void main(String[] args) {
ScheduledExecutorService exc = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> handle = exc.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
i++;
if (i==6) {
throw new RuntimeException();
} else {
System.out.print(i + " ");
}
}
}, 0, 1, TimeUnit.SECONDS);
try {
handle.get();
} catch(Exception ex) {
System.out.println();
System.out.println("在ScheduledExecutorService中有异常抛出,异常堆栈:" + ex.getStackTrace());
}
}
}
这个解决方法打印了异常信息,但是并没有阻止线程挂掉。
1 2 3 4 5
在ScheduledExecutorService中有异常抛出,异常堆栈:[Ljava.lang.StackTraceElement;@33909752
总结
一个ScheduledExecutorService启动的Java线程无故挂掉的原因是:如果使用者抛出异常,ScheduledExecutorService 将会停止线程的运行,而且不会报错,没有任何提示信息。解决方法是:try-catch
将异常信息打印,或者用ScheduledFuture<?>获取线程运行结果。
写的bug多,自然经验就多了,但是要注意总结。
完
2018年12月13日09:08:19
一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考的更多相关文章
- 如何停止一个正在运行的java线程
与此问题相关的内容主要涉及三部分:已废弃的Thread.stop().迷惑的thread.interrupt系列.最佳实践Shared Variable. 已废弃的Thread.stop() @Dep ...
- Vert.x,一个异步、可伸缩、并发应用框架引发的思考
2012年听说过Vert.x这个框架之后,去年大致了解了下,最近开始进一步熟悉这个框架. Vert.x是一个用于下一代异步.可伸缩.并发应用的框架,旨在为JVM提供一个Node.js的替代方案.开发者 ...
- Java线程:概念与使用
Java线程大总结 原文章地址:一篇很老的专栏,但是现在看起来也感觉深受启发,知识点很多,很多线程特点我没有看,尴尬.但是还是整理了一下排版,转载一下. 操作系统中线程和进程的概念 在现代操作系统中, ...
- java线程初写,陆续更新中。。
(1)什么是线程?线程,是程序执行流的最小单元.线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享 ...
- Java问题定位之Java线程堆栈分析
采用Java开发的大型应用系统越来越大,越来越复杂,很多系统集成在一起,整个系统看起来像个黑盒子.系统运行遭遇问题(系统停止响应,运行越来越慢,或者性能低下,甚至系统宕掉),如何速度命中问题的根本原因 ...
- Java线程之创建线程
翻译自:https://www.journaldev.com/1016/java-thread-example 进程 进程是一个自包含的执行环境,它可以被看成一个程序或应用程序.然而一个应用程序本身包 ...
- 面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?
摘要: 原创出处 https://studyidea.cn 「公众号:程序通事 」欢迎关注和转载,保留摘要,谢谢! 使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而 ...
- 迅雷笔试题 (JAVA多线程)启动三个线程,分别打印A B C,现在写一个程序 循环打印ABCABCABC
题目:http://wenku.baidu.com/view/d66187aad1f34693daef3e8a.html 启动三个线程,分别打印A B C,现在写一个程序 循环打印ABCABCABC. ...
- Java线程状态、线程start方法源码、多线程、Java线程池、如何停止一个线程
下面将依次介绍: 1. 线程状态.Java线程状态和线程池状态 2. start方法源码 3. 什么是线程池? 4. 线程池的工作原理和使用线程池的好处 5. ThreadPoolExecutor中的 ...
随机推荐
- vue子组件的自定义事件
父子组件的信息传递无碍就是父组件给子组件传值(props和$attrs)和父组件触发子组件的事件($emit) 之前已经谈过了父组件给子组件传值了,现在来说说父组件触发子组件的自定义事件吧-- 实际上 ...
- S折交叉验证(S-fold cross validation)
S折交叉验证(S-fold cross validation) 觉得有用的话,欢迎一起讨论相互学习~Follow Me 仅为个人观点,欢迎讨论 参考文献 https://blog.csdn.net/a ...
- Hadoop Yarn源码 - day2
接着昨天的继续看hadoop-yarn-api,昨天看了api package下的4个协议,今天来看下con package下的代码 conf目录下的内容比较少,就4个文件分别是Configurati ...
- Spring Mvc Web 配置拦截规则与访问静态资源 (三)
拦截规则配置 1. *.do <!-- Processes application requests --> <servlet> <servlet-name>app ...
- C# 解决VS2008在win7找不到输入序列号的地方
1.VS2008在Windows7 打开维护界面看不到可以输序列号的地方. 因为微软把他隐藏了. 2.我们可以借用工具把他显示出来 下载地址:http://www.zlsoft.com/techbbs ...
- linux笔记_day10_shell编程
1.shell编程 编程语言 静态语言:编译型语言 强类型(变量在使用前,必须事先声明) 事先转换成可执行语言 动态语言:解释型语言 弱类型(变量用时声明,拿来直接用,甚至不区分数据类型,一般默认都为 ...
- PCA和白化练习之处理二维数据
在很多情况下,我们要处理的数据的维度很高,需要提取主要的特征进行分析这就是PCA(主成分分析),白化是为了减少各个特征之间的冗余,因为在许多自然数据中,各个特征之间往往存在着一种关联,为了减少特征之间 ...
- APMServ5.2.6win10系统Apache、MySQL5.1启动失败解决办法
今天想在本地测试网站源码能否正常运行,如果可以就转空间了,然而下载了APMServ之后发现系统Apache.MySQL5.1启动均失败,小白的人表示只能借助百度,用了一个小时的时间终于解决了,虽然坎坷 ...
- Ibatis.Net 数据库操作学习(四)
一.查询select 还记得第一篇示例中是如何读出数据库里3条数据的吗?就是调用了一个QueryForList方法,从方法名就知道,查询返回列表. 1.QueryForList 返回List< ...
- iis6 和iis7s上整个网站重定向
iis6 和iis7s上整个网站重定向 重定向作用: 重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置.举例说明:就像我XX公司,之前用的网络域名是“www.bb. ...