1. 背景
在 jstack 的内容中可以看到以下的 MySQL Statement Cancellation Timer 守护线程, 在业务高峰期的时候会出现大量的这类守护线程, 由此追溯该线程的生命周期过程;
"MySQL Statement Cancellation Timer" #20647 daemon prio=5 os_prio=0 tid=0x00007f2d087e9800 nid=0xfb83 in Object.wait() [0x00007f2b4b45a000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.util.TimerThread.mainLoop(Timer.java:552)
- locked <0x00000005da147038> (a java.util.TaskQueue)
at java.util.TimerThread.run(Timer.java:505)
Locked ownable synchronizers:
- None
"MySQL Statement Cancellation Timer" #24138 daemon prio=5 os_prio=0 tid=0x00007f402802c800 nid=0x4cf64 in Object.wait() [0x00007f3e49453000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at java.util.TimerThread.mainLoop(Timer.java:526)
- locked <0x00000005f606cc60> (a java.util.TaskQueue)
at java.util.TimerThread.run(Timer.java:505)
Locked ownable synchronizers:
- None
2. TimerThread
java.util.TimerThread 是 Timer.java 文件里的一个内部类, 主要负责 Timer 队列任务的执行和调度;
根据定位 Timer.java:526 位置的代码, 当前状态 WAITING (on object monitor), 表示当前的 timer 线程池为空, 正在等待新入驻;
根据定位 Timer.java:552 位置的代码, 当前状态 TIMED_WAITING (on object monitor) 表示任务等待被激活;
3. getCancelTimer
根据线程名称 MySQL Statement Cancellation Timer 继续追溯, 在 com.mysql.jdbc.ConnectionImpl#getCancelTimer 方法中找到该 TimerThread 的创建(cancelTimer):
4. getCancelTimer 的上游调用
主要是 mysql-connector-java-xxx.jar 中负责 sql 查询的 Statement
5. 创建 CancelTask timeoutTask
在 com.mysql.jdbc.StatementImpl#executeQuery 方法中可以发现, 当启用 queryTimeout 且 timeoutInMillis!=0 时, 在执行 sql 的时候就会创建一个 CancelTask 的线程来控制超时; (后面那个 versionMeetsMinimum 是个版本判断可以先忽略)
然后在项目的 application.yml 中发现配置 mybatis.configuration.default-statement-timeout: 5, 所以 mybatis 在每次的数据库查询都会加上 queryTimeout, 且该配置对全局 SQL 生效, 包括 insert, select, update;
6. CancelTask 执行过程
在 com.mysql.jdbc.StatementImpl.CancelTask#run 方法中, 会另起一个线程, 判断如果启用了 queryTimeoutKillsConnection 的配置时, 会调用当前 Statement 对应的 Connection 里的 realClose 方法;
在 realClose 方法里发现会关闭 cancelTimer 线程;
7. Connection 关闭时
在 com.mysql.jdbc.ConnectionImpl#close 方法里也会发现有 realClose 方法的调用, 即在连接关闭时也会处理 cancelTimer 的释放
8. 总结 MySQL Statement Cancellation Timer 线程的流程
设置了 queryTimeout 会使 jdbc driver 在每次查询数据库时新建 CancelTask(timeoutTask对象) 线程来处理超时, 并使用 CancelTimer(在 ConnectionImpl类中) 来进行调度;
如果 SQL 查询超时了, 则会在 timeoutTask 的 run 方法里调用 com.mysql.jdbc.ConnectionImpl#realClose 来释放 CancelTimer;
如果 Connection 正常关闭 close 时, 也会调用 com.mysql.jdbc.ConnectionImpl#realClose 来释放 CancelTimer;