一、Timer

Timer是JAVA自带的定时任务类,实现如下:

public?class?MyTimerTask?{????
    public?static?void?main(String[]?args)?{????????
        //?定义一个任务???????
    ?   TimerTask?timerTask?=?new?TimerTask()?{????????????
        @Override????????????
            public?void?run()?{????????????????
            System.out.println("打印当前时间:"?+?new?Date());????
????????    }???????
?        };????????
        //?计时器???????
?       Timer?timer?=?new?Timer();???????
?       //?开始执行任务 (延迟1000毫秒执行,每3000毫秒执行一次)????????
        timer.schedule(timerTask,?1000,?3000);????
    }
}

Timer 优缺点分析

优点是使用简单,缺点是当添加并执行多个任务时,前面任务的执行用时和异常将影响到后面任务,这边深海建议谨慎使用。

二、ScheduledExecutorService

ScheduledExecutorService也是Java自带的类,

它可以实现Timer具备的所有功能,并解决了 Timer类存在的问题

实现如下:

public?class?MyScheduledExecutorService?{????
    public?static?void?main(String[]?args)?{????????
        //?创建任务队列???10?为线程数量??????
        ScheduledExecutorService?scheduledExecutorService?=?
                Executors.newScheduledThreadPool(10);?
        //?执行任务??????
??      scheduledExecutorService.scheduleAtFixedRate(()?->?{??????????
??          System.out.println("打印当前时间:"?+?new?Date());??????
??      },?1,?3,?TimeUnit.SECONDS);?//?1s?后开始执行,每?3s?执行一次???

? }
}

ScheduledExecutorService优缺点分析

优点是,该类是JDK1.5自带的类,使用简单,缺点是该方案仅适用于单机环境。

三、Spring Task

Spring系列框架中Spring Framework自带的定时任务,

使用上面两种方式,很难实现某些特定需求,比如每周一执行某任务,但SpringTask可轻松实现。

以SpringBoot为例来实现:

1、开启定时任务

在SpringBoot的启动类上声明 @EnableScheduling:

@SpringBootApplication
@EnableScheduling?//开启定时任务
public?class?DemoApplication?{??
??   // --  -- 
}

2、添加定时任务

只需使用@Scheduled注解标注即可,

如果有多个定时任务,可以创建多个@Scheduled标注的方法,示例如下:

import?org.springframework.scheduling.annotation.Scheduled;
import?org.springframework.stereotype.Component;
@Component?//?把此类托管给?Spring,不能省略
public?class?TaskUtils?{????
    //?添加定时任务????
    @Scheduled(cron?=?"30 40 23 0?0?5")?//?cron表达式:每周一?23:40:30?执行????
    public?void?doTask(){????????
        System.out.println("我是定时任务~");????
    }
}

Spring Boot 启动后会自动加载并执行定时任务,无需手动操作。

Cron 表达式

Spring Task 的实现需要使用 cron 表达式来声明执行的频率和规则,cron 表达式是由 6 位或者 7 位组成的(最后一位可以省略),每位之间以空格分隔,每位从左到右代表的含义如下:

其中 * 和 号都表示匹配所有的时间。

cron 表达式在线生成地址:https://cron.qqe2/

知识扩展:分布式定时任务

上面的方法都是关于单机定时任务的实现,如果是分布式环境可以使用 Redis 来实现定时任务。

使用 Redis实现延迟任务的方法大体可分为两类:通过 ZSet 的方式和键空间通知的方式

1、ZSet 实现方式

通过 ZSet 实现定时任务的思路是,将定时任务存放到 ZSet 集合中,并且将过期时间存储到 ZSet 的 Score 字段中,然后通过一个无线循环来判断当前时间内是否有需要执行的定时任务,如果有则进行执行,具体实现代码如下:

import?redis.clients.jedis.Jedis;
import?utils.JedisUtils;
import?java.time.Instant;
import?java.util.Set;
public?class?DelayQueueExample?{????????
    private?static?final?String?_KEY?=?"DelayQueueExample";????????
    public?static?void?main(String[]?args)?throws?InterruptedException?{????????
        Jedis?jedis?=?JedisUtils.getJedis();????????
        //?30s?后执行????????
        long?delayTime?=?Instant.now().plusSeconds(30).getEpochSecond();???????
?       jedis.zadd(_KEY,?delayTime,?"order_1");????????
        //?继续添加测试数据????????
       jedis.zadd(_KEY,?Instant.now().plusSeconds(2).getEpochSecond(),?"order_2");???????
?     jedis.zadd(_KEY,?Instant.now().plusSeconds(2).getEpochSecond(),?"order_3");????????
      jedis.zadd(_KEY,?Instant.now().plusSeconds(7).getEpochSecond(),?"order_4");????????
     jedis.zadd(_KEY,?Instant.now().plusSeconds(10).getEpochSecond(),?"order_5");????????
        //?开启定时任务队列????????
        doDelayQueue(jedis);????
    }????
    /**?????
    *?定时任务队列消费?????
    *?@param?jedis?Redis?客户端?????
    */????
    public?static?void?doDelayQueue(Jedis?jedis)?throws?InterruptedException?{????????
        while?(true)?{????????????
            //?当前时间????????????
            Instant?nowInstant?=?Instant.now();????????????
            long?lastSecond?=?nowInstant.plusSeconds(-1).getEpochSecond();?
            //?上一秒时间????????????
            long?nowSecond?=?nowInstant.getEpochSecond();????????????
            //?查询当前时间的所有任务????????????
            Set?data?=?jedis.zrangeByScore(_KEY,?lastSecond,?nowSecond);????????????
            for?(String?item?:?data)?{????????????????
            //?消费任务????????????????
            System.out.println("消费:"?+?item);????????????
        }????????????
        //?删除已经执行的任务????????????
        jedis.zremrangeByScore(_KEY,?lastSecond,?nowSecond);????????????
        Thread.sleep(1000);?//?每秒查询一次????????
        }????
    }
}

2、键空间通知

我们可以通过 Redis 的键空间通知来实现定时任务,它的实现思路是给所有的定时任务设置一个过期时间,等到了过期之后,我们通过订阅过期消息就能感知到定时任务需要被执行了,此时我们执行定时任务即可。

默认情况下 Redis 是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex 的命令手动开启,开启之后定时任务的代码如下:

import?redis.clients.jedis.Jedis;
import?redis.clients.jedis.JedisPubSub;
import?utils.JedisUtils;
public?class?TaskExample?{????
    public?static?final?String?_TOPIC?=?"__keyevent@0__:expired";?//?订阅频道名称???
    public?static?void?main(String[]?args)?{???????
?       Jedis?jedis?=?JedisUtils.getJedis();???????
?       //?执行定时任务????????
        doTask(jedis);????
    }???
?    /**?????
       *?订阅过期消息,执行定时任务?????
       *?@param?jedis?Redis?客户端?????
       */????
    public?static?void?doTask(Jedis?jedis)?{????????
        //?订阅过期消息????????
        jedis.psubscribe(new?JedisPubSub()?{????????????
            @Override????????????
 public?void?onPMessage(String?pattern,?String?channel,?String?message)?{????????????????
            //?接收到消息,执行定时任务????????????????
            System.out.println("收到消息:"?+?message);????????????
            }???????    ?
        },?_TOPIC);????
    }
}

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

更多推荐

Java 定时任务-最简单的3种实现方法