# Quartz教程 - 4 Job

Quartz 每次在执行任务的时候,都会创建新的 Job 对象和 JobDetail 对象。

修改 Job 对象,添加打印 job 和 jobDetail 对象信息,如下:

package com.doubibiji.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyJob implements Job {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
      
      	// 打印 job 和 jobDetail
        System.out.println("执行任务:" + sdf.format(new Date()) + ", job:" + System.identityHashCode(this));
        System.out.println("执行任务:" + sdf.format(new Date()) + ", jobDetail:" + System.identityHashCode(context.getJobDetail()));

        System.out.println("--------");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

执行结果:

每次打印日志的 Job 和 JobDetail 对象都是不一样的,也就是每次执行任务都会创建新的 Job 和 JobDetail 对象。

为什么要每次创建一个新的对象呢,因为可能任务执行比较久,一次任务没执行完,下一次任务就开始执行的问题,如果使用的是同一个对象,就可能存在并发问题。

# 4.1 禁止并发

但是如果不想要并发执行,而是想要上一次任务执行完成才可以执行下一次的任务,变成串行执行,该如何处理呢?

可以为 Job 类添加 @DisallowConcurrentExecution 注解来禁止并发执行同一个 Job定义(JobDetail定义的)的多个实例。

举个栗子:

给 Job 类添加 @DisallowConcurrentExecution 注解,我们使用的还是之前使用的触发器,每1秒执行一次,但是我们的任务每次执行需要花费3秒执行。

package com.doubibiji.job;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;
import java.util.Date;

@DisallowConcurrentExecution
public class MyJob implements Job {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        try {
            System.out.println("执行任务:" + sdf.format(new Date()));

            Thread.sleep(3000);
        } catch(Exception e) {
            e.printStackTrace();
        }

        System.out.println("--------");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

如果没有 @DisallowConcurrentExecution 注解,虽然任务没有执行完成,但是每过1秒都会执行一次新的任务,导致任务会一直累积。

而添加了 @DisallowConcurrentExecution 注解,上一次执行完成,才会执行下一次任务。

执行结果:

可以看到每隔三秒执行一次,不会并发执行,而是变成了串行执行。

# 4.2 Job的状态

每次执行任务都是新的 JobDetail 和 Job 实例,那么在传递数据的时候,每次的 JobDataMap 都是相同的数据。

这个就是无状态的 Job。即使我们在 Job 类中,修改了 JobDataMap 中的数据,也不会对下一次的执行产生影响。


如果想要用一个计数器记录执行的次数,即我们想要 JobDataMap 的数据能一直保存下来应该如何设置呢?

这个就需要有状态的 Job 了。

举个栗子:

使用 count 记录任务执行的次数,首先在 JobDetail 中初始化 count 的值。

// 1.定义jobDetail
JobDetail job = JobBuilder.newJob(MyJob.class)
        .withIdentity("job1", "jGroup1")
        .usingJobData("count", 0)           // 初始化count的值
        .build();
1
2
3
4
5

修改 Job 类

需要给 Job 类添加 @PersistJobDataAfterExecution 注解。

package com.doubibiji.job;

import org.quartz.*;

import java.text.SimpleDateFormat;
import java.util.Date;

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class MyJob implements Job {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        // 获取值
        int count = jobDataMap.getInt("count");
        count++;

        System.out.println("执行任务:" + sdf.format(new Date()) + ", count:" + count);

        // 将count的值重新保存到JobDataMap
        jobDataMap.put("count", count);

        try {
            Thread.sleep(3000);
        } catch(Exception e) {
            e.printStackTrace();
        }

        System.out.println("--------");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

首先从 JobDataMap 中获取到 count,然后修改 count 的值,然后重新将 count 的值保存到 JobDataMap 中,这样下一次任务执行的时候,就可以获取到更新后的值。

执行结果:

这里需要注意,需要给 Job 类同时添加 @DisallowConcurrentExecution 注解,让任务串行执行,否则当前任务没有执行完成,下一次任务执行的时候,通过 JobDataMap 获取到的值是没有被更新的。

执行结果就会变成下面这样:

另外,有状态任务对应的JobDataMap在每次执行任务后都会进行保存。但是不管是有状态还是无状态的 Job,Trigger 的 JobDataMap 都是无状态的,是无法保存的。