定时器

场景

  • 指定时间点执行任务
  • 指定时间间隔执行任务
  • 在二的基础上指定次数执行任务(场景较少)

线程轮询+Sleep

最简单的实现方法是启动一个线程,设定时间间隔或时间点,进入循环后,调用Sleep睡眠对应时间间隔,然后执行任务(放入线程池执行任务)

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
public class ScheduleThread extends Thread {
public ScheduleThread() {
super();
super.setDaemon(true);
}
public void run() {
try {
for (;;) {
logger.debug("Cache task schedule thread running.");
doRun();
Thread.sleep(CacheManager.scheduleInterval);
}
} catch (Exception x) {
logger.error("Shedule cache task exception.", x);
} finally {
try {
Thread.sleep(CacheManager.scheduleInterval);
} catch (InterruptedException e) {}
}
}
public void doRun() {
// if 间隔模式 放入线程池执行任务
// else if 时间点模式 获取任务,判断是否到达时间点,是则放入线程池执行任务
}
}

Timer & TimerTask

Timer内部维护一个TaskQueue和TaskThread守护线程,Timer实例化时启动守护线程,线程会轮询TaskQueue任务(这里与第一种方法类似),Timer接收TimerTask后放入TaskQueue,按执行时间排序,轮询逻辑里面使用了synchronized同步方法,Timer简单但相比第一种自定义方式,执行任务是单线程同步执行,任务执行时间会影响后面的任务,效率非常不好。

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
public class MyTimerTask {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new MyTask1(), 2 * 1000); // 等待2秒后执行任务
timer.schedule(new MyTask3(), 5 * 1000, 3 * 1000); // 等待5秒后执行任务,每间隔3秒再执行任务
try {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date taskTm = df.parse("2018-05-28 23:51:00"); // 指定时间点执行任务
timer.schedule(new MyTask2(), taskTm);
} catch (ParseException e) {
System.out.println(e);
}
}
}
class MyTask1 extends TimerTask {
public void run() {
System.out.println(String.format("%s :run task 1", new Date().getTime()));
}
}
class MyTask2 extends TimerTask {
public void run() {
System.out.println(String.format("%s :run task 2", new Date().getTime()));
}
}
class MyTask3 extends TimerTask {
public void run() {
System.out.println(String.format("%s :run task 3", new Date().getTime()));
}
}

ScheduledExecutorService

JDK里面提供ScheduledExecutor改进了Timer,引入线程池执行调度任务,就是我们实现的第一种方法,jdk做了优化封装;

  • 间隔时间模式

    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
    public class ScheduledExecutorTest {
    public static void main(String[] args) {
    ScheduledExecutorService service = Executors.newScheduledThreadPool(10); // 创建初始化10个线程的线程池
    service.schedule(new Job1(), 1, TimeUnit.SECONDS); // 延迟1秒后执行
    service.scheduleAtFixedRate(new Job2(), 1, 2, TimeUnit.SECONDS); // 延迟1秒后执行,然后每间隔2秒执行 (任务执行时间不影响)
    service.scheduleWithFixedDelay(new Job3(), 1, 3, TimeUnit.SECONDS); // 延迟1秒后执行,执行完后再间隔3秒执行(该任务自身串行执行)
    }
    }
    class Job1 implements Runnable {
    @Override
    public void run() {
    System.out.println("execute job1");
    }
    }
    class Job2 implements Runnable {
    @Override
    public void run() {
    System.out.println("execute job2");
    }
    }
    class Job3 implements Runnable {
    @Override
    public void run() {
    System.out.println("execute job3");
    }
    }
  • 指定时间点模式

    该模式没有直接实现,可以变通成,按时间点与当前时间对比做判断,设置需要延迟多久进行执行(schedule方法),如果是每周或每月,则需要计算最早执行时间点,然后用scheduleAtFixedRate或scheduleWithFixedDelay实现;

Quartz+java

ScheduledExecutorService的问题在于比较复杂的调度情况时候,逻辑复杂,线程池需要自己评估,调度配置硬编码耦合,所以有了任务调度框架,工作里面使用最多的就是Quartz

  • maven 坐标

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
    </dependency>
  • 例子,cron方式可以把表达式独立到配置中

    格式:秒 分 小时 月内日期 月 周内日期 年(optional)
    “n/m” 字符:增量值, m时间循环一次
    “?” 字符:月内日期和周内日期使用,表示这个位置忽略
    “*” 字符:表示任意值
    Cron 表达式在线工具

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class MyJob implements Job {
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    System.out.println(new Date());
    }
    public static void main(String[] args) throws SchedulerException {
    // job to jobDetail
    JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();
    // trigger
    Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group")
    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
    .build();
    Trigger trigger2 = TriggerBuilder.newTrigger().withIdentity("trigger2", "group")
    .withSchedule(CronScheduleBuilder.cronSchedule("0 11 17 ? * MON-FRI"))
    .build();
    // scheduler
    Scheduler scheduler = new StdSchedulerFactory().getScheduler();
    // jobDetail trigger to scheduler
    scheduler.scheduleJob(jobDetail, trigger2);
    // start
    scheduler.start();
    }
    }
  • 体系结构(网上找的图 )
    quartz

    1、Job和Trigger之间1对多关系
    2、trigger不止上面两种,还有DateIntervalTrigger和NthIncludedDayTrigger
    3、Job的类型:stateless、stateful(有状态只能串行执行)
    4、Scheduler调度线程:常规调度线程(轮询trigger), misfired调度线程(轮询检查misfired trigger, 根据misfired策略处理)
    5、Quartz 自带一个线程池的实现:SimpleThreadPool, 线程池大小会影响性能以及misfired,在配置中设置org.quartz.threadPool.class进行替换扩展
    6、misfired策略
    MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:针对 misfired job 马上执行一次;
    MISFIRE_INSTRUCTION_DO_NOTHING:忽略 misfired job,等待下次触发;
    7、存储方式:RAMJobStore(基于内存,快),JobStoreSupport(持久化到数据库,实现优雅停机,导致速度变慢,由StdJDBCDelegate代理实现)

    1
    2
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    org.quartz.jobStore.isClustered = true

Quartz+Spring

1
2
3
4
5
6
public class MyTask {
private Logger logger = LoggerFactory.getLogger(getClass());
public void execute() {
logger.info("execute task...")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- Job -->
<bean id="task" class="bin.sudo.MyTask" />
<!-- JobDetail -->
<bean id="taskDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="task" />
<property name="targetMethod" value="execute" />
<property name="concurrent" value="false" />
</bean>
<!-- Trigger -->
<bean id="taskTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="taskDetail" />
<!-- 格式: [秒] [分] [小时] [日] [月] [周] [年] -->
<property name="cronExpression" value="*/10 * * * * ?" />
</bean>
<!-- Scheduler -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="taskTrigger" />
<!--其他trigger-->
</list>
</property>
</bean>

Quartz+Springboot

  • Job

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @PersistJobDataAfterExecution
    @DisallowConcurrentExecution // 禁止并发执行
    public class MyJob extends QuartzJobBean {
    private ConfigUtil configUtil;
    protected void init(JobExecutionContext context) {
    final JobDataMap jobDataMap = context.getMergedJobDataMap();
    configUtil = (ConfigUtil) jobDataMap.get("configUtil");
    }
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    init(context); // 利用context传入实例
    // do something
    }
    }
  • JobDetail、trigger、scheduler

    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
    36
    37
    38
    39
    40
    @Configuration
    public class MyJobConfig {

    @Value("${cron}")
    private String cron; // cron配置

    @Autowired
    private ConfigUtil configUtil;

    @Bean(name = "myJobDetailFactoryBean")
    public JobDetailFactoryBean firstJobDetailFactoryBean() {
    JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
    jobDetailFactory.setJobClass(MyJob.class);
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("configUtil", configUtil); // 传入其他实例
    jobDetailFactory.setJobDataAsMap(data);
    jobDetailFactory.setDurability(true);
    return jobDetailFactory;
    }

    @Bean(name = "myCronTriggerFactoryBean")
    public CronTriggerFactoryBean firstCronTriggerFactoryBean(
    @Qualifier("myJobDetailFactoryBean") JobDetailFactoryBean jobDetailFactory) {
    CronTriggerFactoryBean cronTriggerFactory = new CronTriggerFactoryBean();
    cronTriggerFactory.setCronExpression(cron);
    cronTriggerFactory.setJobDetail(jobDetailFactory.getObject());
    return cronTriggerFactory;
    }

    @Bean(name = "mySchedulerFactoryBean")
    public SchedulerFactoryBean schedulerFactoryBean(
    @Qualifier("myJobDetailFactoryBean") JobDetailFactoryBean myJobDetail,
    @Qualifier("myCronTriggerFactoryBean") CronTriggerFactoryBean myTrigger) {
    SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
    scheduler.setJobDetails(myJobDetail.getObject());
    scheduler.setTriggers(myTrigger.getObject()); // 可添加多个trigger
    return scheduler;
    }

    }
  • springboot 使用注解方式 FIXME

Spring schedule FIXME

Spring自己提供了一个schedule调度模块,配置更简单了,但听说性能不行
这里有个使用方法

心跳检查 & Timing Wheel

场景

  • 网络编程的心跳逻辑,有netty的实现
  • 大量定时任务进行超时判断,有kafka的实现
  • 游戏里面也有大量的buff时间判断
  • 剔除空闲连接
  • 扩展:是否可以作为业务状态的超时判断?

链接

Linux 时间

###