thread dump 线程状态以及线程的定义

简介: thread dump 线程状态

周末看到一个用 jstack 查看死锁的例子。昨天晚上总结了一下 jstack (查看线程)、jmap (查看内存) 和 jstat (性能分析) 命令。供大家参考

1.Jstack

1.1   jstack 能得到运行 java 程序的 java stack 和 native stack 的信息。可以轻松得知当前线程的运行情况。如下图所示

注:这个和 thread dump 是同样的结果。但是 thread dump 是用 kill -3 pid 命令,还是服务器上面少用 kill 为妙

1.2   命名行格式

jstack [ option ] pid

jstack [ option ] executable core

jstack [ option ] [server-id@]remote-hostname-or-IP

最常用的还是 jstack  pid


1.3   在 thread dump 中,要留意下面几种状态

死锁,Deadlock(重点关注)

等待资源,Waiting on condition(重点关注)

・  等待获取监视器,Waiting on monitor entry(重点关注)

阻塞,Blocked(重点关注)

・  执行中,Runnable

・  暂停,Suspended

・  对象等待中,Object.wait () 或 TIMED_WAITING

・  停止,Parked

下面有详细的例子讲这种分析,大家参考原著

http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html 


1.4   在 thread dump 中,有几种线程的定义如下

线程名称 所属 解释说明

Attach Listener JVM Attach Listener 线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。通常我们会用一些命令去要求 jvm 给我们一些反馈信息,如:java -version、jmap、jstack 等等。 如果该线程在 jvm 启动的时候没有初始化,那么,则会在用户第一次执行 jvm 命令时,得到启动。

 

Signal Dispatcher JVM 前面我们提到第一个 Attach Listener 线程的职责是接收外部 jvm 命令,当命令接收成功后,会交给 signal dispather 线程去进行分发到各个不同的模块处理命令,并且返回处理结果。 signal dispather 线程也是在第一次接收外部 jvm 命令时,进行初始化工作。

 

CompilerThread0 JVM 用来调用 JITing,实时编译装卸 class 。 通常,jvm 会启动多个线程来处理这部分工作,线程名称后面的数字也会累加,例如:CompilerThread1

 

Concurrent Mark-Sweep GC Thread JVM 并发标记清除垃圾回收器(就是通常所说的 CMS GC)线程, 该线程主要针对于老年代垃圾回收。ps:启用该垃圾回收器,需要在 jvm 启动参数中加上: -XX:+UseConcMarkSweepGC

DestroyJavaVM JVM 执行 main () 的线程在 main 执行完后调用 JNI 中的 jni_DestroyJavaVM () 方法唤起 DestroyJavaVM 线程。   JVM 在 Jboss 服务器启动之后,就会唤起 DestroyJavaVM 线程,处于等待状态,等待其它线程(java 线程和 native 线程)退出时通知它卸载 JVM。线程退出时,都会判断自己当前是否是整个 JVM 中最后一个非 deamon 线程,如果是,则通知 DestroyJavaVM 线程卸载 JVM。

 

ps:

扩展一下:

1. 如果线程退出时判断自己不为最后一个非 deamon 线程,那么调用 thread->exit (false) ,并在其中抛出 thread_end 事件,jvm 不退出。

2. 如果线程退出时判断自己为最后一个非 deamon 线程,那么调用 before_exit () 方法,抛出两个事件:  事件 1:thread_end 线程结束事件、事件 2:VM 的 death 事件。

   然后调用 thread->exit (true) 方法,接下来把线程从 active list 卸下,删除线程等等一系列工作执行完成后,则通知正在等待的 DestroyJavaVM 线程执行卸载 JVM 操作。

ContainerBackgroundProcessor 线程 JBOSS 它是一个守护线程,在 jboss 服务器在启动的时候就初始化了,主要工作是定期去检查有没有 Session 过期。过期则清除.

参考:http://liudeh-009.iteye.com/blog/1584876 

 

Dispatcher-Thread-3  线程 Log4j  Log4j 具有异步打印日志的功能,需要异步打印日志的 Appender 都需要注册到 AsyncAppender 对象里面去,由 AsyncAppender 进行监听,决定何时触发日志打印操作。 AsyncAppender 如果监听到它管辖范围内的 Appender 有打印日志的操作,则给这个 Appender 生成一个相应的 event,并将该 event 保存在一个 buffuer 区域内。  Dispatcher-Thread-3 线程负责判断这个 event 缓存区是否已经满了,如果已经满了,则将缓存区内的所有 event 分发到 Appender 容器里面去,那些注册上来的 Appender 收到自己的 event 后,则开始处理自己的日志打印工作。 Dispatcher-Thread-3 线程是一个守护线程。

Finalizer 线程 JVM 这个线程也是在 main 线程之后创建的,其优先级为 10,主要用于在垃圾收集前,调用对象的 finalize () 方法;关于 Finalizer 线程的几点:

  1) 只有当开始一轮垃圾收集时,才会开始调用 finalize () 方法;因此并不是所有对象的 finalize () 方法都会被执行;

  2) 该线程也是 daemon 线程,因此如果虚拟机中没有其他非 daemon 线程,不管该线程有没有执行完 finalize () 方法,JVM 也会退出;

  3) JVM 在垃圾收集时会将失去引用的对象包装成 Finalizer 对象(Reference 的实现),并放入 ReferenceQueue,由 Finalizer 线程来处理;最后将该 Finalizer 对象的引用置为 null,由垃圾收集器来回收;

  4) JVM 为什么要单独用一个线程来执行 finalize () 方法呢?如果 JVM 的垃圾收集线程自己来做,很有可能由于在 finalize () 方法中误操作导致 GC 线程停止或不可控,这对 GC 线程来说是一种灾难;

 

Gang worker#0 JVM JVM 用于做新生代垃圾回收(monir gc)的一个线程。# 号后面是线程编号,例如:Gang worker#1

 

GC Daemon JVM GC Daemon 线程是 JVM 为 RMI 提供远程分布式 GC 使用的,GC Daemon 线程里面会主动调用 System.gc () 方法,对服务器进行 Full GC。 其初衷是当 RMI 服务器返回一个对象到其客户机(远程方法的调用方)时,其跟踪远程对象在客户机中的使用。当再没有更多的对客户机上远程对象的引用时,或者如果引用的 “租借” 过期并且没有更新,服务器将垃圾回收远程对象。

不过,我们现在 jvm 启动参数都加上了 - XX:+DisableExplicitGC 配置,所以,这个线程只有打酱油的份了。

 

IdleRemover JBOSS Jboss 连接池有一个最小值, 该线程每过一段时间都会被 Jboss 唤起,用于检查和销毁连接池中空闲和无效的连接,直到剩余的连接数小于等于它的最小值。

 

Java2D Disposer JVM  这个线程主要服务于 awt 的各个组件。 说起该线程的主要工作职责前,需要先介绍一下 Disposer 类是干嘛的。 Disposer 提供一个 addRecord 方法。 如果你想在一个对象被销毁前再做一些善后工作,那么,你可以调用 Disposer#addRecord 方法,将这个对象和一个自定义的 DisposerRecord 接口实现类,一起传入进去,进行注册。  

         Disposer 类会唤起 “Java2D Disposer” 线程,该线程会扫描已注册的这些对象是否要被回收了,如果是,则调用该对象对应的 DisposerRecord 实现类里面的 dispose 方法。

         Disposer 实际上不限于在 awt 应用场景,只是 awt 里面的很多组件需要访问很多操作系统资源,所以,这些组件在被回收时,需要先释放这些资源。

 

InsttoolCacheScheduler_

QuartzSchedulerThread Quartz  InsttoolCacheScheduler_QuartzSchedulerThread 是 Quartz 的主线程,它主要负责实时的获取下一个时间点要触发的触发器,然后执行触发器相关联的作业 。

        原理大致如下:

        Spring 和 Quartz 结合使用的场景下,Spring IOC 容器初始化时会创建并初始化 Quartz 线程池(TreadPool),并启动它。刚启动时线程池中每个线程都处于等待状态,等待外界给他分配 Runnable(持有作业对象的线程)。

        继而接着初始化并启动 Quartz 的主线程(InsttoolCacheScheduler_QuartzSchedulerThread),该线程自启动后就会处于等待状态。等待外界给出工作信号之后,该主线程的 run 方法才实质上开始工作。run 中会获取 JobStore 中下一次要触发的作业,拿到之后会一直等待到该作业的真正触发时间,然后将该作业包装成一个 JobRunShell 对象(该对象实现了 Runnable 接口,其实看是上面 TreadPool 中等待外界分配给他的 Runnable),然后将刚创建的 JobRunShell 交给线程池,由线程池负责执行作业。

线程池收到 Runnable 后,从线程池一个线程启动 Runnable,反射调用 JobRunShell 中的 run 方法,run 方法执行完成之后, TreadPool 将该线程回收至空闲线程中。

 

InsttoolCacheScheduler_Worker-2 Quartz InsttoolCacheScheduler_Worker-2 线程就是 ThreadPool 线程的一个简单实现,它主要负责分配线程资源去执行

InsttoolCacheScheduler_QuartzSchedulerThread 线程交给它的调度任务(也就是 JobRunShell)。

 

JBossLifeThread Jboss    Jboss 主线程启动成功,应用程序部署完毕之后将 JBossLifeThread 线程实例化并且 start,JBossLifeThread 线程启动成功之后就处于等待状态,以保持 Jboss Java 进程处于存活中。  所得比较通俗一点,就是 Jboss 启动流程执行完毕之后,为什么没有结束? 就是因为有这个线程 hold 主了它。 牛 b 吧~~

JBoss System Threads (1)-1 Jboss   该线程是一个 socket 服务,默认端口号为: 1099。 主要用于接收外部 naming service(Jboss  JNDI)请求。

 

JCA PoolFiller Jboss     该线程主要为 JBoss 内部提供连接池的托管。  简单介绍一下工作原理 :

   Jboss 内部凡是有远程连接需求的类,都需要实现 ManagedConnectionFactory 接口,例如需要做 JDBC 连接的

XAManagedConnectionFactory 对象,就实现了该接口。然后将 XAManagedConnectionFactory 对象,还有其它信息一起包装到 InternalManagedConnectionPool 对象里面,接着将 InternalManagedConnectionPool 交给 PoolFiller 对象里面的列队进行管理。   JCA PoolFiller 线程会定期判断列队内是否有需要创建和管理的 InternalManagedConnectionPool 对象,如果有的话,则调用该对象的 fillToMin 方法, 触发它去创建相应的远程连接,并且将这个连接维护到它相应的连接池里面去。

 

JDWP Event Helper Thread JVM            

JDWP 是通讯交互协议,它定义了调试器和被调试程序之间传递信息的格式。它详细完整地定义了请求命令、回应数据和错误代码,保证了前端和后端的 JVMTI 和 JDI 的通信通畅。  该线程主要负责将 JDI 事件映射成 JVMTI 信号,以达到调试过程中操作 JVM 的目的。  



JDWP Transport Listener: dt_socket JVM 该线程是一个 Java Debugger 的监听器线程,负责受理客户端的 debug 请求。 通常我们习惯将它的监听端口设置为 8787。

Low Memory Detector JVM 这个线程是负责对可使用内存进行检测,如果发现可用内存低,分配新的内存空间。

process reaper JVM     该线程负责去执行一个 OS 命令行的操作。

Reference Handler JVM         JVM 在创建 main 线程后就创建 Reference Handler 线程,其优先级最高,为 10,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题 。

Surrogate Locker Thread (CMS) JVM           这个线程主要用于配合 CMS 垃圾回收器使用,它是一个守护线程,其主要负责处理 GC 过程中,Java 层的 Reference(指软引用、弱引用等等)与 jvm 内部层面的对象状态同步。 这里对它们的实现稍微做一下介绍:这里拿 WeakHashMap 做例子,将一些关键点先列出来(我们后面会将这些关键点全部串起来):

 

1.  我们知道 HashMap 用 Entry [] 数组来存储数据的,WeakHashMap 也不例外,内部有一个 Entry [] 数组。

2.  WeakHashMap 的 Entry 比较特殊,它的继承体系结构为 Entry->WeakReference->Reference 。

3.  Reference 里面有一个全局锁对象:Lock,它也被称为 pending_lock.    注意:它是静态对象。

4.  Reference  里面有一个静态变量:pending。

5.  Reference  里面有一个静态内部类:ReferenceHandler 的线程,它在 static 块里面被初始化并且启动,启动完成后处于 wait 状态,它在一个 Lock 同步锁模块中等待。

6.  另外,WeakHashMap 里面还实例化了一个 ReferenceQueue 列队,这个列队的作用,后面会提到。

7.  上面关键点就介绍完毕了,下面我们把他们串起来。

   

  假设,WeakHashMap 对象里面已经保存了很多对象的引用。 JVM 在进行 CMS GC 的时候,会创建一个 ConcurrentMarkSweepThread(简称 CMST)线程去进行 GC,ConcurrentMarkSweepThread 线程被创建的同时会创建一个 SurrogateLockerThread(简称 SLT)线程并且启动它,SLT 启动之后,处于等待阶段。CMST 开始 GC 时,会发一个消息给 SLT 让它去获取 Java 层 Reference 对象的全局锁:Lock。 直到 CMS GC 完毕之后,JVM 会将 WeakHashMap 中所有被回收的对象所属的 WeakReference 容器对象放入到 Reference 的 pending 属性当中(每次 GC 完毕之后,pending 属性基本上都不会为 null 了),然后通知 SLT 释放并且 notify 全局锁:Lock。此时激活了 ReferenceHandler 线程的 run 方法,使其脱离 wait 状态,开始工作了。ReferenceHandler 这个线程会将 pending 中的所有 WeakReference 对象都移动到它们各自的列队当中,比如当前这个 WeakReference 属于某个 WeakHashMap 对象,那么它就会被放入相应的 ReferenceQueue 列队里面(该列队是链表结构)。 当我们下次从 WeakHashMap 对象里面 get、put 数据或者调用 size 方法的时候,WeakHashMap 就会将 ReferenceQueue 列队中的 WeakReference 依依 poll 出来去和 Entry [] 数据做比较,如果发现相同的,则说明这个 Entry 所保存的对象已经被 GC 掉了,那么将 Entry [] 内的 Entry 对象剔除掉。

 

taskObjectTimerFactory JVM           顾名思义,该线程就是用来执行任务的。 当我们把一个认为交给 Timer 对象,并且告诉它执行时间,周期时间后,Timer 就会将该任务放入任务列队,并且通知 taskObjectTimerFactory 线程去处理任务,taskObjectTimerFactory 线程会将状态为取消的任务从任务列队中移除,如果任务是非重复执行类型的,则在执行完该任务后,将它从任务列队中移除,如果该任务是需要重复执行的,则计算出它下一次执行的时间点。

 

VM Periodic Task Thread JVM         该线程是 JVM 周期性任务调度的线程,它由 WatcherThread 创建,是一个单例对象。 该线程在 JVM 内使用得比较频繁,比如:定期的内存监控、JVM 运行状况监控,还有我们经常需要去执行一些 jstat 这类命令查看 gc 的情况,如下:

jstat -gcutil 23483 250 7   这个命令告诉 jvm 在控制台打印 PID 为:23483 的 gc 情况,间隔 250 毫秒打印一次,一共打印 7 次。

 

VM Thread JVM          这个线程就比较牛 b 了,是 jvm 里面的线程母体,根据 hotspot 源码(vmThread.hpp)里面的注释,它是一个单例的对象(最原始的线程)会产生或触发所有其他的线程,这个单个的 VM 线程是会被其他线程所使用来做一些 VM 操作(如,清扫垃圾等)。

        在 VMThread 的结构体里有一个 VMOperationQueue 列队,所有的 VM 线程操作 (vm_operation) 都会被保存到这个列队当中,VMThread 本身就是一个线程,它的线程负责执行一个自轮询的 loop 函数 (具体可以参考:VMThread.cpp 里面的 void VMThread::loop ()) ,该 loop 函数从 VMOperationQueue 列队中按照优先级取出当前需要执行的操作对象 (VM_Operation),并且调用 VM_Operation->evaluate 函数去执行该操作类型本身的业务逻辑。

      ps:VM 操作类型被定义在 vm_operations.hpp 文件内,列举几个:ThreadStop、ThreadDump、PrintThreads、GenCollectFull、GenCollectFullConcurrent、CMS_Initial_Mark、CMS_Final_Remark….. 有兴趣的同学,可以自己去查看源文件。

(搬运自 http://blog.csdn.net/a43350860/article/details/8134234 感谢原著作者)


2.Jmap

2.1   得到运行 java 程序的内存分配的详细情况。例如实例个数,大小等


2.2   命名行格式

jmap [ option ] pid

jmap [ option ] executable core

jmap [ option ] [server-id@]remote-hostname-or-IP


-dump:[live,] format=b,file=<filename> 使用 hprof 二进制形式,输出 jvm 的 heap 内容到文件 =. live 子选项是可选的,假如指定 live 选项,那么只输出活的对象到文件.

-finalizerinfo 打印正等候回收的对象的信息.

-heap 打印 heap 的概要信息,GC 使用的算法,heap 的配置及 wise heap 的使用情况.

-histo [:live] 打印每个 class 的实例数目,内存占用,类全名信息. VM 的内部类名字开头会加上前缀”*”. 如果 live 子参数加上后,只统计活的对象数量.

-permstat 打印 classload 和 jvm heap 长久层的信息。包含每个 classloader 的名字,活泼性,地址,父 classloader 和加载的 class 数量。另外,内部 String 的数量和占用内存数也会打印出来.

-F 强迫。在 pid 没有相应的时候使用 - dump 或者 - histo 参数。在这个模式下,live 子参数无效.

-h | -help 打印辅助信息

-J 传递参数给 jmap 启动的 jvm.


2.3   使用例子

jmap -histo pid (查看实例)

 

 

jmap -J-d64 -dump:live,format=b,file=heap_dump.bin pid

jmap -dump:format=b,file=heap.bin pid (导出内存,据说对性能有影响,小心使用)

(format=b 是通过二进制的意思,但是能不能导出文本文件我没找到,知道的告诉我)

把内存结构全部 dump 到二进制文件中,通过 IBM 的 HeapAnalyzer 和 eclipse 的 MemoryAnalyzer 都可以分析内存结构。

这个是我用 HeapAnalyzer 查看出的我们 daily 的内存结构,已经列出了可能存在的问题。(这个工具我不熟悉,只供大家参考)

 

下面是我用 eclipse 的 MemoryAnalyzer 查看内存结构图

 

 

 

 

上面的是 eclipse 分析内存泄漏分析出的。这个功能点非常多。可以慢慢学习



3.Jstat

3.1   这是一个比较实用的一个命令,可以观察到 classloader,compiler,gc 相关信息。可以时时监控资源和性能


3.2      命令格式

-class:统计 class loader 行为信息

-compile:统计编译行为信息

-gc:统计 jdk gc 时 heap 信息

-gccapacity:统计不同的 generations(不知道怎么翻译好,包括新生区,老年区,permanent 区)相应的 heap 容量情况

-gccause:统计 gc 的情况,(同 - gcutil)和引起 gc 的事件

-gcnew:统计 gc 时,新生代的情况

-gcnewcapacity:统计 gc 时,新生代 heap 容量

-gcold:统计 gc 时,老年区的情况

-gcoldcapacity:统计 gc 时,老年区 heap 容量

-gcpermcapacity:统计 gc 时,permanent 区 heap 容量

-gcutil:统计 gc 时,heap 情况


3.3   输出参数内容

S0  — Heap 上的 Survivor space 0 区已使用空间的百分比

S0C:S0 当前容量的大小

S0U:S0 已经使用的大小

S1  — Heap 上的 Survivor space 1 区已使用空间的百分比

S1C:S1 当前容量的大小

S1U:S1 已经使用的大小

E   — Heap 上的 Eden space 区已使用空间的百分比

EC:Eden space 当前容量的大小

EU:Eden space 已经使用的大小

O   — Heap 上的 Old space 区已使用空间的百分比

OC:Old space 当前容量的大小

OU:Old space 已经使用的大小

P   — Perm space 区已使用空间的百分比

OC:Perm space 当前容量的大小

OU:Perm space 已经使用的大小

YGC — 从应用程序启动到采样时发生 Young GC 的次数

YGCT– 从应用程序启动到采样时 Young GC 所用的时间 (单位秒)

FGC — 从应用程序启动到采样时发生 Full GC 的次数

FGCT– 从应用程序启动到采样时 Full GC 所用的时间 (单位秒)

GCT — 从应用程序启动到采样时用于垃圾回收的总时间 (单位秒),它的值等于 YGC+FGC

 

例子 1

 

例子 2 (连续 5 次)

 

例子 3 (PGCMN 显示的是最小 perm 的内存使用量,PGCMX 显示的是 perm 的内存最大使用量,PGC 是当前新生成的 perm 内存占用量,PC 是但前 perm 内存占用量)

 

这个工具的参数非常多,据说基本能覆盖 jprofile 等收费工具的所有功能了。多用用对于系统调优还是很有帮助的



注 1:我们在 daily 用这样命令时,都要用 - F 参数的。因为我们的用户都不是启动命令的用户

注 2:daily 的这些命令好像都没有配置到环境变量里面,这个是我在自己应用机器里面看到的。需要去 jdk 目录底下执行。Sudo 当然是必须的了

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3天前
|
Java 中间件 API
【C/C++ 线程 】深入浅出:理解 std::thread 的局限性
【C/C++ 线程 】深入浅出:理解 std::thread 的局限性
53 2
|
3天前
|
存储 前端开发 算法
C++线程 并发编程:std::thread、std::sync与std::packaged_task深度解析(一)
C++线程 并发编程:std::thread、std::sync与std::packaged_task深度解析
61 0
|
3天前
|
存储 并行计算 Java
C++线程 并发编程:std::thread、std::sync与std::packaged_task深度解析(二)
C++线程 并发编程:std::thread、std::sync与std::packaged_task深度解析
74 0
|
2天前
|
关系型数据库 MySQL Java
实时计算 Flink版产品使用合集之mysql通过flink cdc同步数据,有没有办法所有表共用一个dump线程
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
7 0
|
3天前
|
安全 Java 调度
Java一分钟:多线程编程初步:Thread类与Runnable接口
【5月更文挑战第11天】本文介绍了Java中创建线程的两种方式:继承Thread类和实现Runnable接口,并讨论了多线程编程中的常见问题,如资源浪费、线程安全、死锁和优先级问题,提出了解决策略。示例展示了线程通信的生产者-消费者模型,强调理解和掌握线程操作对编写高效并发程序的重要性。
45 3
|
3天前
|
Java API 调度
【Java多线程】Thread类的基本用法
【Java多线程】Thread类的基本用法
10 0
|
3天前
|
算法 安全 调度
【C++入门到精通】 线程库 | thread类 C++11 [ C++入门 ]
【C++入门到精通】 线程库 | thread类 C++11 [ C++入门 ]
17 1
|
3天前
|
存储 机器学习/深度学习 C++
thread(线程)
**Lua中的协同程序(coroutine)类似线程,有独立栈和局部变量,但它们不能并行,只能单次运行,通过挂起切换。** \n\n**Userdata是自定义数据类型,允许存储C/C++的任意数据到Lua,常用于struct和指针。**
|
3天前
|
Java
Java中的多线程实现:使用Thread类与Runnable接口
【4月更文挑战第8天】本文将详细介绍Java中实现多线程的两种方法:使用Thread类和实现Runnable接口。我们将通过实例代码展示如何创建和管理线程,以及如何处理线程同步问题。最后,我们将比较这两种方法的优缺点,以帮助读者在实际开发中选择合适的多线程实现方式。
25 4
|
3天前
|
Java Spring
springboot单类集中定义线程池
该内容是关于Spring中异步任务的配置和使用步骤。首先,在启动类添加`@EnableAsync`注解开启异步支持。然后,自定义线程池类`EventThreadPool`,设置核心和最大线程数、存活时间等参数。接着,将线程池bean注入到Spring中,如`@Bean(&quot;RewardThreadPool&quot;)`。最后,在需要异步执行的方法上使用`@Async`注解,例如在一个定时任务类中,使用`@Scheduled(cron = &quot;...&quot;)`和`@Async`结合实现异步定时任务。
19 2