文章目录
- 1. 现象
- 2. 结论
- 3. 相关代码
- 4. 查看堆栈:
- 5. 查看submitCall
- 5.1 ThreadPoolExecutor#execute最终调用了 RunnableFuture#run方法
- 5.2 从代码层面判断 futureTask.get超时只影响了业务线程(调用futureTask.get的线程),不影响工作线程。
- 5.3 future.get
- 5.3.1 测试future.get并不能打断线程池的线程。
- 6. 查看工作线程为何阻塞
- 6.1 修复
- 7. 相关资料
1. 现象 线上发短信、邮箱验证码 的时候超时
文章图片
2. 结论
- SocketInputStream.socketRead0导致线程阻塞,阻塞后占用了线程池的线程。多次阻塞后最终占用了全部的core线程。新提交的任务只能入队,没有线程来处理。
由于 socket.read占用了corePoolSize 个 线程池的工作线程worker.thread , 这里一共有10个,全都阻塞了。
而execute提交一个runnable的时候, 在达到corePoolSize后, 会将其放入workQueue中。直到workQueue满。
新的任务只能入队(enQueue),不能被消费。
所以 futureTask.get 一直超时。
- futureTask.get(timeout,timeunit)不会导致线程池的工作线程异常。工作线程会继续执行。
文章图片
文章图片
文章图片
WorkerThreadPool
这里workQueue本身是一个优先队列,这里会无限扩容
ps:由于无限扩容, 这里maxinumPoolSize是无效的
文章图片
文章图片
文章图片
先查找相关调用链的底层日志,发现根本没有调用底层方法。
而其他应用能调用到该底层,一直在输出日志,说明是execute本身有问题。
4. 查看堆栈: 搜索AsyncWorker(ps:自定义的线程池一定要重命名,找问题的时候方便),发现该线程池的10个core线程都处于runnable,且所有线程都在
sendEmail
文章图片
5. 查看submitCall 虽然future.get有超时,但是这只能保证业务线程不阻塞。
future.get并不能打断线程池的线程。
文章图片
这里的sendVcWorkerThreadPool#submitCall与ThreadPoolExecutor
ThreadPoolExecutor#submit类似
文章图片
由于继承ThreadPoolExecutor,所以调用了ThreadPoolExecutor的execute
文章图片
5.1 ThreadPoolExecutor#execute最终调用了 RunnableFuture#run方法
- 调用链
addWorker()->w.start()->treahd.run()->Worker.runWorker(Worker w)->task.run();
- task即RunnableFuture ,newTaskFor创建了子类FutureTask
- FutureTask是对Callable的一层封装。
- 超时只影响了业务线程(调用futureTask.get的线程),不影响工作线程。
FutureTask.run
文章图片
运行完毕,设置结果
此时可以使用future.get出结果
文章图片
这里让【因为future.get,调用park方法使得等待】的线程 恢复。
文章图片
5.3 future.get
文章图片
死循环检测是否完成, 超时后,直接return 当前state
Unsafe.park()本地方法休眠当前线程, HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。
文章图片
休眠
文章图片
检测的时候,先将当前线程添加到waitNode
文章图片
5.3.1 测试future.get并不能打断线程池的线程。
6. 查看工作线程为何阻塞 虽然已经证明了futureTask.get超时后不会打断线程池的worker.thread,还是需要查看工作线程为何阻塞。
再回顾一下堆栈
文章图片
execute的调用链是addWorker()->w.start()->treahd.run()->Worker.runWorker(Worker w)->task.run();
而FutureTask是对Callable的一层封装。
本身是SendEmailCall本身是一个Callable
我们只需要查看SendEmailCall的call方法为何一直在运行。
文章图片
transport.connect
文章图片
文章图片
文章图片
文章图片
【Java|SocketInputStream.socketRead0引起线程池提交任务后,futureTask.get超时】阻塞到readLine
文章图片
到这里就很明显了, 是socket的inputStream调用read的时候阻塞。
文章图片
socket是可以设置timeout的。
查找timeout的设置
文章图片
getSocket
文章图片
to为read超时时间,
cto为连接超时时间
如果不设置则都为永久
文章图片
而创建的时候,没有设置timeout
到此就明白了, 设置timeout即可。
文章图片
6.1 修复
props.put("mail.smtp.connectiontimeout", "3000");
props.put("mail.smtp.timeout", "3000");
文章图片
文章图片
debug测试
文章图片
修改后
文章图片
修改为超短时间 会报错。
文章图片
7. 相关资料
线程池中的线程何时死亡?
SocketInputStream.socketRead0引起线程池提交任务后,futureTask.get超时
socket连接代理socketRead0(Native Method) 线程阻塞处理
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)