简要剖析xxljob的使用和原理
一、使用场景
XXL-JOB是什么 XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。
为什么要叫 XXL 呢?答:是因为他的作者的名字叫许雪里,使用了名字的缩写
分布式任务调度平台是什么呢?答:一个定时任务实现方
优点是什么呢:(大家都懂,免费,简单,能满足需求)
1.xxljob是分布式的,可以满足大部分现在的分布式业务需求。
2.文档比较全,对开发人员友善,咱们都知道文档的重要性,少走很多弯路。
3.有界面。操作可视化,简单明了。
4.最重要就是部署简单,方便快捷。
二、服务搭建
我们使用目前主流的docker安装的方式(主要是省事)
1.使用mysql创建需要的数据库表,结构如下:
2.搭建admin服务
1.拉取镜像 docker pull xuxueli/xxl-job-admin:2.3.0
2.执行dockerfile文件或者直接命令行启动,方便起见直接后者
docker run -d --name xxl-job-admin -p 4090:8080 \
-e PARAMS="\
--spring.datasource.url=jdbc:mysql://ip地址:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai \
--spring.datasource.username=xxl \
--spring.datasource.password=123456" \
-v /usr/local/xxl-job/admin/logs:/data/applogs \
--privileged=true \
--name xxl-job \
xuxueli/xxl-job-admin:2.3.0
3.查看容器状态, docker ps
如果正常就说明安装好了
注意事项:
服务器部署之后要开放端口号。建议开放端口号别关防火墙。基本防护意识要有,即使是测试或者自己服务器;
# 1、开启防火墙
systemctl start firewalld
# 2、开放指定端口(比如4090端口)
firewall-cmd --zone=public --add-port=4090/tcp --permanent
# 3、重启防火墙
firewall-cmd --reload
# 4、查看端口号
netstat -ntlp #查看当前所有tcp端口·
三、基本使用和demo案例
一般就是引入使用一个中间件的四件套
1.引依赖
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.1</version>
</dependency>
2.写yaml
你的properties建议写nacos上方便后期热更新
# web port
server.port=8081
# no web
#spring.main.web-environment=false
# log config
logging.config=classpath:logback.xml
### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### xxl-job, access token
xxl.job.accessToken=default_token
### xxl-job executor appname
xxl.job.executor.appname=xxl-job-executor-sample
### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
xxl.job.executor.port=9999
### xxl-job executor log-path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job executor log-retention-days
xxl.job.executor.logretentiondays=30
3.创建配置类
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.address}")
private String address;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
4.写任务
package com.xxl.job.executor.service.jobhandler;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* XxlJob开发示例(Bean模式)
*
* 开发步骤:
* 1、任务开发:在Spring Bean实例中,开发Job方法;
* 2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
* 3、执行日志:需要通过 "XxlJobHelper.log" 打印执行日志;
* 4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果;
*
* @author xuxueli 2019-12-11 21:52:51
*/
@Component
public class SampleXxlJob {
private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);
/**
* 1、简单任务示例(Bean模式)
*/
@XxlJob("demoJobHandler")
public void demoJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {
XxlJobHelper.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
// default success
}
/**
* 2、分片广播任务
*/
@XxlJob("shardingJobHandler")
public void shardingJobHandler() throws Exception {
// 分片参数
int shardIndex = XxlJobHelper.getShardIndex();
int shardTotal = XxlJobHelper.getShardTotal();
XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);
// 业务逻辑
for (int i = 0; i < shardTotal; i++) {
if (i == shardIndex) {
XxlJobHelper.log("第 {} 片, 命中分片开始处理", i);
} else {
XxlJobHelper.log("第 {} 片, 忽略", i);
}
}
}
/**
* 3、命令行任务
*/
@XxlJob("commandJobHandler")
public void commandJobHandler() throws Exception {
String command = XxlJobHelper.getJobParam();
int exitValue = -1;
BufferedReader bufferedReader = null;
try {
// command process
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(command);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
//Process process = Runtime.getRuntime().exec(command);
BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream());
bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
// command log
String line;
while ((line = bufferedReader.readLine()) != null) {
XxlJobHelper.log(line);
}
// command exit
process.waitFor();
exitValue = process.exitValue();
} catch (Exception e) {
XxlJobHelper.log(e);
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
}
if (exitValue == 0) {
// default success
} else {
XxlJobHelper.handleFail("command exit value("+exitValue+") is failed");
}
}
/**
* 4、跨平台Http任务
* 参数示例:
* "url: http://www.baidu.com\n" +
* "method: get\n" +
* "data: content\n";
*/
@XxlJob("httpJobHandler")
public void httpJobHandler() throws Exception {
// param parse
String param = XxlJobHelper.getJobParam();
if (param==null || param.trim().length()==0) {
XxlJobHelper.log("param["+ param +"] invalid.");
XxlJobHelper.handleFail();
return;
}
String[] httpParams = param.split("\n");
String url = null;
String method = null;
String data = null;
for (String httpParam: httpParams) {
if (httpParam.startsWith("url:")) {
url = httpParam.substring(httpParam.indexOf("url:") + 4).trim();
}
if (httpParam.startsWith("method:")) {
method = httpParam.substring(httpParam.indexOf("method:") + 7).trim().toUpperCase();
}
if (httpParam.startsWith("data:")) {
data = httpParam.substring(httpParam.indexOf("data:") + 5).trim();
}
}
// param valid
if (url==null || url.trim().length()==0) {
XxlJobHelper.log("url["+ url +"] invalid.");
XxlJobHelper.handleFail();
return;
}
if (method==null || !Arrays.asList("GET", "POST").contains(method)) {
XxlJobHelper.log("method["+ method +"] invalid.");
XxlJobHelper.handleFail();
return;
}
boolean isPostMethod = method.equals("POST");
// request
HttpURLConnection connection = null;
BufferedReader bufferedReader = null;
try {
// connection
URL realUrl = new URL(url);
connection = (HttpURLConnection) realUrl.openConnection();
// connection setting
connection.setRequestMethod(method);
connection.setDoOutput(isPostMethod);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setReadTimeout(5 * 1000);
connection.setConnectTimeout(3 * 1000);
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
// do connection
connection.connect();
// data
if (isPostMethod && data!=null && data.trim().length()>0) {
DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
dataOutputStream.write(data.getBytes("UTF-8"));
dataOutputStream.flush();
dataOutputStream.close();
}
// valid StatusCode
int statusCode = connection.getResponseCode();
if (statusCode != 200) {
throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");
}
// result
bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
StringBuilder result = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
String responseMsg = result.toString();
XxlJobHelper.log(responseMsg);
return;
} catch (Exception e) {
XxlJobHelper.log(e);
XxlJobHelper.handleFail();
return;
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (Exception e2) {
XxlJobHelper.log(e2);
}
}
}
/**
* 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
*/
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
public void demoJobHandler2() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
}
public void init(){
logger.info("init");
}
public void destroy(){
logger.info("destroy");
}
}
四、原理分析
一、注解分析
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface XxlJob {
String value();
String init() default "";
String destroy() default "";
}
@XxlJob
注解中的init
参数和destroy
参数
init
参数
- 用途:指定在任务执行之前需要执行的方法名称。
- 典型用例:初始化任务所需的资源、配置、环境等。
- 执行时机:在任务实际执行之前调用。
destroy
参数
-
用途:指定在任务执行之后需要执行的方法名称。
-
典型用例:清理任务执行过程中使用的资源、释放占用的资源、记录日志等。
-
执行时机:在任务实际执行完毕之后调用。
简单demo
import com.xxl.job.core.handler.annotation.XxlJob;
public class MyJob {
// 任务方法
@XxlJob(value = "myJobHandler", init = "initMethod", destroy = "destroyMethod")
public void execute() {
// 任务执行的具体逻辑
System.out.println("Executing job...");
}
// 初始化方法
public void initMethod() {
// 初始化逻辑,例如资源准备
System.out.println("Initializing resources...");
}
// 销毁方法
public void destroyMethod() {
// 清理逻辑,例如资源释放
System.out.println("Releasing resources...");
}
}
注解中的这些方法,会在注册任务执行器的时候添加到任务执行器中;
在XxlJobExecutor类中会有这样一个方法:protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod)
executeMethod.setAccessible(true);
Method initMethod = null;
Method destroyMethod = null;
if (xxlJob.init().trim().length() > 0) {
try {
initMethod = clazz.getDeclaredMethod(xxlJob.init());
initMethod.setAccessible(true);
} catch (NoSuchMethodException var11) {
throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
}
}
if (xxlJob.destroy().trim().length() > 0) {
try {
destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
destroyMethod.setAccessible(true);
} catch (NoSuchMethodException var10) {
throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
}
}
//注册任务执行器,传入一个方法执行器,传入的参数包括初始化和注销方法,作用是将任务执行器存放到一个ConcurrentHashMap中,方便后面进行执行
registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
MethodJobHandler
类如下:
public class MethodJobHandler extends IJobHandler {
private final Object target;
private final Method method;
private Method initMethod;
private Method destroyMethod;
public MethodJobHandler(Object target, Method method, Method initMethod, Method destroyMethod) {
this.target = target;
this.method = method;
this.initMethod = initMethod;
this.destroyMethod = destroyMethod;
}}
二、调用链路分析
在XxlJobConfig中会注入一个XxlJobSpringExecutor的bean对象,用来初始化执行器的配置。
这个XxlJobSpringExecutor对象打开源码我们会看到是这样一个类:
public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean{}
我们可以看到这个类总共实现了三个接口,分别是
ApplicationContextAware
:对应用程序上下文的引用,实现 ApplicationContextAware
接口的 Bean 可以获取对 Spring 应用程序上下文的引用,从而能够访问应用程序上下文中的其他 Bean、配置信息、资源等;也可以访问ioc容器中的其他bean;
SmartInitializingSingleton
:SmartInitializingSingleton
是 Spring 框架中的一个接口,它只有一个方法 afterSingletonsInstantiated
。这个方法在 Spring 容器中所有单例 Bean 都初始化完成之后会被调用。在执行器中主要担任的作用就是获取到所有需要执行的xxljob任务;
DisposableBean
:主要就是销毁后执行的方法。
在这里我们先知道这些接口的作用,等一会进行链路分析的时候碰到了再详细讲解:
这个类中自己本身的方法主要有两个我们来看一下
-
实现
SmartInitializingSingleton 接口的afterSingletonsInstantiated
方法public void afterSingletonsInstantiated() { //进行初始化任务方法注册器 this.initJobHandlerMethodRepository(applicationContext); GlueFactory.refreshInstance(1); try { //父类的开始方法 super.start(); } catch (Exception var2) { throw new RuntimeException(var2); } }
-
initJobHandlerMethodRepository
方法private void initJobHandlerMethodRepository(ApplicationContext applicationContext) { if (applicationContext != null) { //获取所有bena的名字 String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true); String[] var3 = beanDefinitionNames; int var4 = beanDefinitionNames.length; for(int var5 = 0; var5 < var4; ++var5) { String beanDefinitionName = var3[var5]; Object bean = null; //获取是否是懒加载的类 Lazy onBean = (Lazy)applicationContext.findAnnotationOnBean(beanDefinitionName, Lazy.class); if (onBean != null) { logger.debug("xxl-job annotation scan, skip @Lazy Bean:{}", beanDefinitionName); } else { //获取对应的bean bean = applicationContext.getBean(beanDefinitionName); Map<Method, XxlJob> annotatedMethods = null; try { //获取指定bean中有xxljob注解的方法 annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(), new MethodIntrospector.MetadataLookup<XxlJob>() { public XxlJob inspect(Method method) { return (XxlJob)AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class); } }); } catch (Throwable var14) { logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", var14); } //遍历方法集合 if (annotatedMethods != null && !annotatedMethods.isEmpty()) { Iterator var10 = annotatedMethods.entrySet().iterator(); while(var10.hasNext()) { Map.Entry<Method, XxlJob> methodXxlJobEntry = (Map.Entry)var10.next(); Method executeMethod = (Method)methodXxlJobEntry.getKey(); XxlJob xxlJob = (XxlJob)methodXxlJobEntry.getValue(); //注册任务执行器(这个方法是父类中的方法) this.registJobHandler(xxlJob, bean, executeMethod); } } } } } }
父类
XxlJobExecutor
中的protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod)
方法protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod) { //获取注解中的方法名和init方法和destroy方法 if (xxlJob != null) { String name = xxlJob.value(); Class<?> clazz = bean.getClass(); String methodName = executeMethod.getName(); if (name.trim().length() == 0) { throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] ."); } else if (loadJobHandler(name) != null) { throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts."); } else { executeMethod.setAccessible(true); Method initMethod = null; Method destroyMethod = null; if (xxlJob.init().trim().length() > 0) { try { initMethod = clazz.getDeclaredMethod(xxlJob.init()); initMethod.setAccessible(true); } catch (NoSuchMethodException var11) { throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] ."); } } if (xxlJob.destroy().trim().length() > 0) { try { destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy()); destroyMethod.setAccessible(true); } catch (NoSuchMethodException var10) { throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] ."); } } //注册任务,将其传入到jobHandlerRepository任务集合中,key为name,值为方法任务执行器 //方法为jobHandlerRepository.put(name, jobHandler); registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod)); } } }
-
super.start()方法:个人认为这就是为啥能够实现热更新定时任务的原因。实时监听admin服务器的接口信息
//实现父类中的方法 public void start() throws Exception { //前面都是设置一些配置信息 XxlJobFileAppender.initLogPath(this.logPath); this.initAdminBizList(this.adminAddresses, this.accessToken); JobLogFileCleanThread.getInstance().start((long)this.logRetentionDays); TriggerCallbackThread.getInstance().start(); //这一步是初始化嵌入服务器也就是为啥 this.initEmbedServer(this.address, this.ip, this.port, this.appname, this.accessToken); }
-
initEmbedServer
的方法主要用途是用于启动一个嵌入式的HTTP服务器。这个服务器使用了Netty框架来处理网络通信。private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception { port = port > 0 ? port : NetUtil.findAvailablePort(9999); ip = ip != null && ip.trim().length() > 0 ? ip : IpUtil.getIp(); if (address == null || address.trim().length() == 0) { String ip_port_address = IpUtil.getIpPort(ip, port); address = "http://{ip_port}/".replace("{ip_port}", ip_port_address); } if (accessToken == null || accessToken.trim().length() == 0) { logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken."); } this.embedServer = new EmbedServer(); //开始执行服务器 this.embedServer.start(address, port, appname, accessToken); }
start()执行服务器
public void start(final String address, final int port, final String appname, final String accessToken) { this.executorBiz = new ExecutorBizImpl(); //构建线程 this.thread = new Thread(new Runnable() { public void run() { //用于接受连接的线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); //用于处理已经被接受连接的线程组。 EventLoopGroup workerGroup = new NioEventLoopGroup(); //自定义线程池 final ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(0, 200, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(2000), new ThreadFactory() { //自定义线程工厂返回线程名字 public Thread newThread(Runnable r) { return new Thread(r, "xxl-job, EmbedServer bizThreadPool-" + r.hashCode()); } }, new RejectedExecutionHandler() { //处理任务拒绝策略,当线程池无法接受新的任务时抛出异常。 public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!"); } }); //ServerBootstrap:Netty服务器的启动辅助类。 bossGroup和workerGroup:设置boss和worker线程组。 NioServerSocketChannel:指定使用NIO传输类型的通道。 childHandler:设置处理每个新连接的处理器。 IdleStateHandler:处理空闲状态的检测。 HttpServerCodec:HTTP请求解码器和响应编码器。 HttpObjectAggregator:将HTTP消息聚合成完整的HTTP请求或响应。 EmbedHttpServerHandler:自定义的业务处理器。 childOption(ChannelOption.SO_KEEPALIVE, true):启用TCP的SO_KEEPALIVE选项,保持长连接。 try { ServerBootstrap bootstrap = new ServerBootstrap(); ((ServerBootstrap)bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<SocketChannel>() { public void initChannel(SocketChannel channel) throws Exception { channel.pipeline().addLast(new ChannelHandler[]{new IdleStateHandler(0L, 0L, 90L, TimeUnit.SECONDS)}).addLast(new ChannelHandler[]{new HttpServerCodec()}).addLast(new ChannelHandler[]{new HttpObjectAggregator(5242880)}).addLast(new ChannelHandler[]{new EmbedHttpServerHandler(EmbedServer.this.executorBiz, accessToken, bizThreadPool)}); } }).childOption(ChannelOption.SO_KEEPALIVE, true); //保存端口启动服务器 ChannelFuture future = bootstrap.bind(port).sync(); EmbedServer.logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port); EmbedServer.this.startRegistry(appname, address); future.channel().closeFuture().sync(); } catch (InterruptedException var16) { EmbedServer.logger.info(">>>>>>>>>>> xxl-job remoting server stop."); } catch (Exception var17) { EmbedServer.logger.error(">>>>>>>>>>> xxl-job remoting server error.", var17); } finally { //优雅的释放资源记录日志 try { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } catch (Exception var15) { EmbedServer.logger.error(var15.getMessage(), var15); } } } }); //守护线程 this.thread.setDaemon(true); this.thread.start(); }
-
-
-
实现
DisposableBean
的destroy()
方法public void destroy() { //执行父类的方法 super.destroy(); } //父类中的方法 public void destroy() { //停止嵌入式的执行器,也就是之前提到的netty框架的服务器 this.stopEmbedServer(); //如果有执行的任务就遍历停止 if (jobThreadRepository.size() > 0) { Iterator var1 = jobThreadRepository.entrySet().iterator(); while(var1.hasNext()) { Map.Entry<Integer, JobThread> item = (Map.Entry)var1.next(); //移除任务线程,并传递销毁原因 JobThread oldJobThread = removeJobThread((Integer)item.getKey(), "web container destroy and kill the job."); if (oldJobThread != null) { try { //等待当前任务执行完毕后再进行操作,确保正在执行的任务能够执行完毕 oldJobThread.join(); } catch (InterruptedException var5) { logger.error(">>>>>>>>>>> xxl-job, JobThread destroy(join) error, jobId:{}", item.getKey(), var5); } } } //释放资源 jobThreadRepository.clear(); } //清除执行器 jobHandlerRepository.clear(); // 停止日志清理线程和回调线程,确保它们不再运行 JobLogFileCleanThread.getInstance().toStop(); TriggerCallbackThread.getInstance().toStop(); }
为什么使用join方法?
使用join方法的目的是确保在执行后续清理操作之前,所有任务线程都已经完全执行完毕。这有几个好处: 保证线程安全:确保所有任务线程安全地终止,避免资源冲突或不一致状态。 清理资源:确保任务线程执行完毕后,再进行资源清理和释放操作。 逻辑完整性:确保任务执行的完整性,不会因为主线程过早继续执行而导致任务线程未执行完毕。
三、写在结尾
自己真正意义上的第一篇文档输出。也是在上个星期和掘友们讨论的:想要提高个人技术水平,除了扩展知识的广度外也要注意知识的深度。除了知道如何用还要知其所以然。 有时候可能知道自己并不能很好的坚持下去,但是有时候有个好的开始已经够了。
我最喜欢的一句话就是,对过程努力,对结果佛系。 春有百花秋有月,夏有蝉鸣冬有雪。有遗憾有不足才是再正常不过的事情了。乐观点看,能开始本身就是极好的,这也是我写这篇文章的原因和内在动力。