平时xxl-job大部分人使用都是单线程的方式,即执行一次任务由一个主线程执行。这里记录一下,xxl-job在线程池异步(多租户场景下)出现项目启动后,第一次调用之后的调用(如第二次、三次..),其传入的任务参数以及日志打印出现问题:任务参数都是第一次调用的,日志打印都打印在第一次调用的log中。
1:错误代码示意
package com.yw.xxljobstudy.job;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class CategoryTaskSyncSchedule {
private static final ThreadPoolExecutor taskExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
@XxlJob("taskTest") // 表示这是一个XXL-JOB的任务执行器中的定时任务,任务名称为"docReview"
public void docReview() {
for (int i = 0; i < 5; i++) {
//线程池,需要传递当前任务上下文
execTask(i);
}
}
public void execTask(Integer n) {
CompletableFuture.runAsync(() -> {
log.info("任务参数:{}",XxlJobHelper.getJobParam());
XxlJobHelper.log("执行任务成功:{},任务参数:{}",n,XxlJobHelper.getJobParam());
}, taskExecutor);
}
}
测试结果:
可以看见第一次调用这里传递给线程池中线程获得的任务参数都是正常的,但第二次调用结果还是一样:
xxl-job调度日志也是错误:
打印到第一次调用里面了:
到这里根据线程池也能猜出端倪,点进XxlJobHelper.getJobParam()看源码:
这里有一个线程变量XxlJobContext,底层是InheritableThreadLocal,这个区别于ThreadLocal,使用于父子线程,子线程可以复用父线程的变量,但咱这里使用的是线程池,不符合。
因为线程池的核心线程一直是存在的,也就导致这个线程内容上下文contextHolder一直都是保存的第一次调用的参数,因为线程一直复用不会销毁移除。从而第二次以及之后的调用都是第一次的任务参数,日志打印错误也是这样的逻辑。
2:正确示意
每次xxl-job执行任务都会创建一个主线程,在run里面去初始化设置XxlJobContext,而主线程会随着任务执行结束销毁,当前的XxlJobContext也会销毁。每次执行初始化的XxlJobContext是我们想要的,所以在往线程池提交任务的时候,设置线程池执行线程当前的XxlJobContext
package com.yw.xxljobstudy.job;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class CategoryTaskSyncSchedule {
private static final ThreadPoolExecutor taskExecutor = new ThreadPoolExecutor(
5,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10));
@XxlJob("taskTest") // 表示这是一个XXL-JOB的任务执行器中的定时任务,任务名称为"docReview"
public void docReview() {
//新加部分
XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
for (int i = 0; i < 5; i++) {
//线程池,需要传递当前任务上下文
execTask(i,xxlJobContext);
}
}
public void execTask(Integer n, XxlJobContext xxlJobContext) {
CompletableFuture.runAsync(() -> {
//设置线程池中执行线程当前任务的上下文(新加部分)
XxlJobContext.setXxlJobContext(xxlJobContext);
log.info("任务参数:{}",XxlJobHelper.getJobParam());
XxlJobHelper.log("执行任务成功:{},任务参数:{}",n,XxlJobHelper.getJobParam());
}, taskExecutor);
}
}
测试:
第一次依旧
二次已经正常了:
查看xxl调度日志也正常: