文章目录

  • 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. 现象

线上发短信、邮箱验证码 的时候超时

java线程池提交任务失败 向线程池提交任务_堆栈

2. 结论

  1. SocketInputStream.socketRead0导致线程阻塞,阻塞后占用了线程池的线程。多次阻塞后最终占用了全部的core线程。新提交的任务只能入队,没有线程来处理。
    由于 socket.read占用了corePoolSize 个 线程池的工作线程worker.thread , 这里一共有10个,全都阻塞了。
    而execute提交一个runnable的时候, 在达到corePoolSize后, 会将其放入workQueue中。直到workQueue满。
    新的任务只能入队(enQueue),不能被消费。
    所以 futureTask.get 一直超时。
  2. futureTask.get(timeout,timeunit)不会导致线程池的工作线程异常。工作线程会继续执行。

3. 相关代码

sendVcWorkerThreadPool是ThreadPoolExecutor的子类WorkerThreadPool

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_02


java线程池提交任务失败 向线程池提交任务_工作线程_03

java线程池提交任务失败 向线程池提交任务_线程池_04


WorkerThreadPool

这里workQueue本身是一个优先队列,这里会无限扩容

ps:由于无限扩容, 这里maxinumPoolSize是无效的

java线程池提交任务失败 向线程池提交任务_线程池_05


java线程池提交任务失败 向线程池提交任务_堆栈_06


java线程池提交任务失败 向线程池提交任务_线程池_07

先查找相关调用链的底层日志,发现根本没有调用底层方法。

而其他应用能调用到该底层,一直在输出日志,说明是execute本身有问题。

4. 查看堆栈:

搜索AsyncWorker(ps:自定义的线程池一定要重命名,找问题的时候方便),发现该线程池的10个core线程都处于runnable,且所有线程都在

sendEmail

java线程池提交任务失败 向线程池提交任务_堆栈_08

5. 查看submitCall

虽然future.get有超时,但是这只能保证业务线程不阻塞。

future.get并不能打断线程池的线程。

java线程池提交任务失败 向线程池提交任务_堆栈_09


这里的sendVcWorkerThreadPool#submitCall与ThreadPoolExecutor

ThreadPoolExecutor#submit类似

java线程池提交任务失败 向线程池提交任务_线程池_10


由于继承ThreadPoolExecutor,所以调用了ThreadPoolExecutor的execute

java线程池提交任务失败 向线程池提交任务_堆栈_11

5.1 ThreadPoolExecutor#execute最终调用了 RunnableFuture#run方法

  • 调用链
    addWorker()->w.start()->treahd.run()->Worker.runWorker(Worker w)->task.run();
  • task即RunnableFuture ,newTaskFor创建了子类FutureTask

因此 查看FutureTask的run方法

  • FutureTask是对Callable的一层封装。
  • 超时只影响了业务线程(调用futureTask.get的线程),不影响工作线程。

5.2 从代码层面判断 futureTask.get超时只影响了业务线程(调用futureTask.get的线程),不影响工作线程。

FutureTask.run

java线程池提交任务失败 向线程池提交任务_工作线程_12

运行完毕,设置结果

此时可以使用future.get出结果

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_13

这里让【因为future.get,调用park方法使得等待】的线程 恢复。

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_14

5.3 future.get

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_15

死循环检测是否完成, 超时后,直接return 当前state

Unsafe.park()本地方法休眠当前线程, HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。

java线程池提交任务失败 向线程池提交任务_堆栈_16


休眠

java线程池提交任务失败 向线程池提交任务_堆栈_17

检测的时候,先将当前线程添加到waitNode

java线程池提交任务失败 向线程池提交任务_线程池_18

5.3.1 测试future.get并不能打断线程池的线程。

java线程池提交任务失败 向线程池提交任务_工作线程_19

6. 查看工作线程为何阻塞

虽然已经证明了futureTask.get超时后不会打断线程池的worker.thread,还是需要查看工作线程为何阻塞。
再回顾一下堆栈

java线程池提交任务失败 向线程池提交任务_堆栈_20


execute的调用链是addWorker()->w.start()->treahd.run()->Worker.runWorker(Worker w)->task.run();

而FutureTask是对Callable的一层封装。

本身是SendEmailCall本身是一个Callable

我们只需要查看SendEmailCall的call方法为何一直在运行。

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_21


transport.connect

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_22


java线程池提交任务失败 向线程池提交任务_堆栈_23


java线程池提交任务失败 向线程池提交任务_工作线程_24


java线程池提交任务失败 向线程池提交任务_工作线程_25

阻塞到readLine

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_26


到这里就很明显了, 是socket的inputStream调用read的时候阻塞。

java线程池提交任务失败 向线程池提交任务_堆栈_27

socket是可以设置timeout的。

查找timeout的设置

java线程池提交任务失败 向线程池提交任务_工作线程_28


getSocket

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_29

to为read超时时间,
cto为连接超时时间
如果不设置则都为永久

java线程池提交任务失败 向线程池提交任务_工作线程_30

而创建的时候,没有设置timeout

到此就明白了, 设置timeout即可。

java线程池提交任务失败 向线程池提交任务_线程池_31

6.1 修复

props.put("mail.smtp.connectiontimeout", "3000");
props.put("mail.smtp.timeout", "3000");

java线程池提交任务失败 向线程池提交任务_工作线程_32


java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_33

debug测试

java线程池提交任务失败 向线程池提交任务_堆栈_34

修改后

java线程池提交任务失败 向线程池提交任务_工作线程_35

修改为超短时间 会报错。

java线程池提交任务失败 向线程池提交任务_java线程池提交任务失败_36

7. 相关资料

线程池中的线程何时死亡?SocketInputStream.socketRead0引起线程池提交任务后,futureTask.get超时socket连接代理socketRead0(Native Method) 线程阻塞处理