java定时调度任务

java常用定时调度任务方式:

1. Timer

2.scheduleThreadPool

3.spring task

4.quartz

5.xxl-job

6. date.calendar.localdatetime

1.Timer

使用方式:

public class Test {
    public static void main(String[] args) {
      Timer time=new Timer();
        TimerTask task=new TimerTask() {
            @Override
            public void run() {
                System.out.println("start!!!!!");
            }
        };
        
        //延迟一秒执行
        time.schedule(task,1000);
        //立刻执行
//      time.schedule(task,System.currentTimeMillis());
//      //延迟一秒执行,每一秒重复执行
//      time.schedule(task,1000,1000);
        //立刻执行
//      time.schedule(task,System.currentTimeMillis(),1000);
//      time.scheduleAtFixedRate(task,1000,1000);
//      time.scheduleAtFixedRate(task,System.currentTimeMillis(),1000);
    }
}

class timer 由一个queue和 timerthread组成,time里面包含内部类threadreaper用于当time对象没有应用或者队列为空时的退出;而time的声明方式只要是名称+是否是守护进程

守护线程与非守护线程:

jvm中规定但不存在非守护进程时jvm就会退出;

也就是有非守护线程时,程序不会退出;而守护线程会随主程序一起退出;

thread.setDeamon(true)设置为守护;默认非守护 

Taskqueue:

初始128大小的数组,扩容每次扩建2倍

本质是一个最小堆,按照执行时间排序,重排序从根往下(任务重复执行),插入从下往上(插入新任务);

 private void fixDown(int k) {
        int j;
        while ((j = k << 1) <= size && j > 0) {
            if (j < size &&
                queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                j++; // j indexes smallest kid
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

 private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

Timerthread

mainloop方法重复执行,通过period区分schedule和scheduleAtFixedRate

period <0,schedule  上一个任务执行完的时间+period

period >0,scheduleAtFixedRate  固定的任务时间+period

优缺点:

优点:简单,容易使用;线程安全;

缺点:

1. 单线程运行,串行化,效率低;

2. 只要有一个任务异常全任务退出;

3. 上一个任务的执行情况影响下一个任务的运行时间;

4. 时间的设置极端情况下只能决定任务的先后顺序,并不能够保证到点执行;

5. 使用system.currentTimeMills()获取时间安排任务,不支持按照年月日执行,只适合执行近期定时任务;

2.scheduleThreadPool

线程池加延迟队列

延迟队列和time的差不多;

优缺点:

优点:基于多线程的定时任务,多任务之间互相不干扰,支持周期性的执行任务,并且带有延迟功能;

缺点:不支持一些复杂的规则设置;

3.SpringTask

cron表达式:

秒、分、时、日、月、周、年


【*】:每的意思。在不同的字段上,就代表每秒,每分,每小时等。
【-】:指定值的范围。比如[1-10],在秒字段里就是每分钟的第1到10秒,在分就是每小时的第1到10分钟,以此类推。
【,】:指定某几个值。比如[2,4,5],在秒字段里就是每分钟的第2,第4,第5秒,以此类推。
【/】:指定值的起始和增加幅度。比如[3/5],在秒字段就是每分钟的第3秒开始,每隔5秒生效一次,也就是第3秒、8秒、13秒,以此类推。
【?】:仅用于【日】和【周】字段。因为在指定某日和周几的时候,这两个值实际上是冲突的,所以需要用【?】标识不生效的字段。比如【0 1 * * * ?】就代表每年每月每日每小时的1分0秒触发任务。这里的周就没有效果了。

0 0 * * * ?每小时一次

0 /5 * * * ?每五分钟一次

0 0  5 ? * 1 每个星期一早上5点

1.基于注解的静态单线程

@Component
@EnableScheduling
public class Test {
    @Scheduled(cron = "0 0 5 ? * 1")
    public int  Schedule1(){
        return 1;
    }

    @Scheduled(cron = "0 5 * ? * *")
    public int  Schedule2(){
        return 1;
    }

    @Scheduled(fixedRate = 1000)
    public int  Schedule3(){
        return 1;
    }

    @Scheduled(fixedDelay = 1000)
    public int  Schedule4(){
        return 1;
    }
}

2.基于接口的动态方法,从数据库动态获取

@Component
@EnableScheduling
class ScheduleConfig implements SchedulingConfigurer{

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

        taskRegistrar.addTriggerTask(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("qqqqq");
                    }
                },(triggerContext) ->{
                    String sql="select cron from scheduled where cron_id=?";
                    int id=1;
                    String cron=jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(String.class),id);
                    return new CronTrigger(cron).nextExecution(triggerContext);
                } 
        );
    }
}

本质上注解默认使用了系统配置的多线程池;

3.基于注解的多线程定时任务 使用@enableasync和@async注解;

@Component
@EnableScheduling
@EnableAsync
class ScheduleConfig implements SchedulingConfigurer{

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Async
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

        taskRegistrar.addTriggerTask(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("qqqqq");
                    }
                },(triggerContext) ->{
                    String sql="select cron from scheduled where cron_id=?";
                    int id=1;
                    String cron=jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(String.class),id);
                    return new CronTrigger(cron).nextExecution(triggerContext);
                }
        );
    }
}

优缺点:

优点:开启定时任务非常方便,支持复杂的cron表达式,单任务完后执行下一个任务;

缺点:默认单线程,不支持集群部署,不能做数据存储型定时任务;

4.quartz

任务名,任务分组,调用目标,执行表达式、计划策略等

job->jobtail 一个任务,多个任务属性

trigger:触发器 crontrigger

schedule:调度器

通过builder创建job\trigger

schedule通过property创建

每一次执行任务使用的是新的jobdetail,并发执行,相互之间并不干扰;

要想串型,使用@DisallowConcurrentExecution在job类上;

每一次执行任务使用的是新的jobdatamap,并发执行,相互之间并不干扰;

要想使用同一个map,@PersistJobDateAfterExecution在job类上;

spingboot整合quartz

1. 监听器

实现applicatoinListerner重写onApplicationEvent生成schdele,并注入trigger和jobdetail

2. schedule

@configture配置

scheduleFactory生成,设置quartproperties,设置线程池,通过工厂模式返回schedule对象;

每个节点跑一个job定义,而不是一个job跑在不同节点上;

锁机制

目前quartz实现分布式任务调度用的是库表行级锁机制,其原理是利用qrtz_locks表保证某个节点只能有一个任务线程获得该行级锁,如果有其他节点想执行同样的任务需要等之前线程释放锁,否则处于等待状态。

也就是说,quartz的分布式集群原理是利用数据库锁来保证操作的顺序性,但是这里有个致命的问题:如果程序中有大量的短周期任务(比如订单处理、库存处理等)需要频繁处理,就会存在各节点的线程抢占数据库锁,从而导致大量线程处于等待状态, 其任务调度的性能将大大受限于业务。

quartz默认使用的有建立的表结构,存储在数据库里,集群工作时,使用表锁实现及合作;

优点:支持多线程,可以做数据存储型任务,维护性高,支持参数传递;

缺点:需要整合spring,不能直接调用业务层service

5.xxl-job

 

6.常见的时间表达方式

        System.out.println(System.currentTimeMillis());
        //1680676759909

        long second = System.currentTimeMillis()/1000;
        long minute = second /60;
        long hour = minute /60;
        System.out.println("h:"+(hour%24+8)+"--m:"+minute%60+"--s:"+second%60);
        //h:14--m:39--s:19

        Date d=new Date();

        LocalDateTime t2=LocalDateTime.now(ZoneId.systemDefault());
        System.out.println(t2.toString());
        //2023-04-05T14:39:19.068845

        LocalDateTime t = d.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        System.out.println(t.getYear()+"-"+t.getMonth().getValue()+"-"+t.getDayOfMonth()+
                "-"+t.getHour()+"-"+t.getMinute()+"-"+t.getSecond());
        //2023-4-5-14-39-19

        Calendar c=Calendar.getInstance();
        long temp=c.getTimeInMillis();
        System.out.println(c.getTime());
        System.out.println(c.getTimeInMillis());
        c.set(2023,3,5,14,58,30);
        System.out.println(c.getTime());
        System.out.println(c.getTimeInMillis());
        System.out.println(c.getTimeInMillis()-temp);
        //Wed Apr 05 15:01:43 CST 2023
        //1680678103637
        //Wed Apr 05 14:58:30 CST 2023
        //1680677910637
        //-193000

相比于date和calendar,

localdatetime不仅线程安全,而且表达方便,更常使用;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值