Java中實(shí)現(xiàn)定時(shí)任務(wù)執(zhí)行的幾種常見方式總結(jié)
一、概述
關(guān)于在Java項(xiàng)目中實(shí)現(xiàn)定時(shí)任務(wù)執(zhí)行的幾種方式總結(jié)如下:
單進(jìn)程實(shí)現(xiàn)定時(shí)任務(wù)主要有以下幾種方式:
1.
Timer和TimerTask(JDK 原生)2.
ScheduledExecutorService(JDK 1.5+,推薦)3.
Spring Task(Spring 框架支持)4.
Quartz(企業(yè)級任務(wù)調(diào)度框架)5. DelayQueue 阻塞隊(duì)列
分布式定時(shí)任務(wù)
1、使用Redis來實(shí)現(xiàn)定時(shí)任務(wù)
2、使用xxl-job實(shí)現(xiàn)定時(shí)任務(wù)
二、詳情
1. Timer 和 TimerTask(JDK 原生)
Java中的Timer類是一個(gè)定時(shí)調(diào)度器,用于在指定的時(shí)間點(diǎn)執(zhí)行任務(wù)。Timer 類用于實(shí)現(xiàn)定時(shí)任務(wù),最大的好處就是他的實(shí)現(xiàn)非常簡單,特別的輕量級,因?yàn)樗荍ava內(nèi)置的,所以只需要簡單調(diào)用就行了。
public class MyTimerTask {
public static void main(String[] args) {
// 定義一個(gè)任務(wù)
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("Run timerTask:" + new Date());
}
};
// 計(jì)時(shí)器
Timer timer = new Timer();
// 添加執(zhí)行任務(wù)(延遲 1s 執(zhí)行,每 3s 執(zhí)行一次)
timer.schedule(timerTask, 1000, 3000);
}
}| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
|
|
2. ScheduledExecutorService(JDK 1.5+,推薦)
ScheduledExecutorService基于線程池(ThreadPoolExecutor),支持多任務(wù)并發(fā)執(zhí)行。相比 Timer,更穩(wěn)定、更靈活。
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
// 延遲 1s 后執(zhí)行
scheduler.schedule(() -> {
System.out.println("Task executed once at: " + new Date());
}, 1, TimeUnit.SECONDS);
// 延遲 1s 后執(zhí)行,之后每 2s 執(zhí)行一次
scheduler.scheduleAtFixedRate(() -> {
System.out.println("Fixed-rate task executed at: " + new Date());
}, 1, 2, TimeUnit.SECONDS);
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
|
|
3. Spring Task(Spring 框架支持)
適合 Spring/Spring Boot 項(xiàng)目,基于 ScheduledExecutorService,提供注解式任務(wù)調(diào)度。
@EnableScheduling
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
@Component
public class TimerTask {
@Scheduled(cron = "0 0 0 0 7 *")
public void task() {
System.out.println("定時(shí)任務(wù)...");
}
}| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 只適合 Spring/Spring Boot 項(xiàng)目 |
4. Quartz(企業(yè)級任務(wù)調(diào)度框架)
Quartz是OpenSymphony開源組織在Job scheduling領(lǐng)域的一個(gè)Java實(shí)現(xiàn)的開源項(xiàng)目。
// 1. 定義 Job
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) {
System.out.println("Quartz Job executed at: " + new Date());
}
}
// 2. 配置 Trigger 和 Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail job = JobBuilder.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 每 5s 執(zhí)行一次
.repeatForever())
.build();
scheduler.scheduleJob(job, trigger);
scheduler.start();| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
|
|
5. DelayQueue 阻塞隊(duì)列
DelayQueue是一個(gè)帶有延遲時(shí)間的無界阻塞隊(duì)列,它的元素必須實(shí)現(xiàn)Delayed接口。當(dāng)從DelayQueue中取出一個(gè)元素時(shí),如果其延遲時(shí)間還未到達(dá),則會阻塞等待,直到延遲時(shí)間到達(dá)。因此,我們可以通過將任務(wù)封裝成實(shí)現(xiàn)Delayed接口的元素,將其放入DelayQueue中,再使用一個(gè)線程不斷地從DelayQueue中取出元素并執(zhí)行任務(wù),從而實(shí)現(xiàn)定時(shí)任務(wù)的調(diào)度。
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayedTask implements Delayed {
private final String taskName;
private final long executeTime; // 執(zhí)行時(shí)間(毫秒時(shí)間戳)
public DelayedTask(String taskName, long delayMs) {
this.taskName = taskName;
this.executeTime = System.currentTimeMillis() + delayMs;
}
@Override
public long getDelay(TimeUnit unit) {
// 返回剩余延遲時(shí)間
long remainingDelay = executeTime - System.currentTimeMillis();
return unit.convert(remainingDelay, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed other) {
// 按執(zhí)行時(shí)間排序(早到期者優(yōu)先)
return Long.compare(this.executeTime, ((DelayedTask) other).executeTime);
}
public void execute() {
System.out.println("Executing task: " + taskName + " at " + new Date(executeTime));
}
}
public class DelayQueueScheduler {
private static final DelayQueue<DelayedTask> queue = new DelayQueue<>();
public static void main(String[] args) throws InterruptedException {
// 提交延遲任務(wù)
queue.put(new DelayedTask("Task 1", 3000)); // 3s 后執(zhí)行
queue.put(new DelayedTask("Task 2", 1000)); // 1s 后執(zhí)行
queue.put(new DelayedTask("Task 3", 5000)); // 5s 后執(zhí)行
// 啟動消費(fèi)者線程處理任務(wù)
while (!queue.isEmpty()) {
DelayedTask task = queue.take(); // 阻塞直到有任務(wù)到期
task.execute();
}
}
}| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
|
|
6、使用 Redis 實(shí)現(xiàn)延遲任務(wù)
使用 Redis 實(shí)現(xiàn)延遲任務(wù)的方法大體可分為兩類:通過 ZSet 的方式和鍵空間通知的方式。
① ZSet 實(shí)現(xiàn)方式
通過 ZSet 實(shí)現(xiàn)定時(shí)任務(wù)的思路是,將定時(shí)任務(wù)存放到 ZSet 集合中,并且將過期時(shí)間存儲到 ZSet 的 Score 字段中,然后通過一個(gè)無線循環(huán)來判斷當(dāng)前時(shí)間內(nèi)是否有需要執(zhí)行的定時(shí)任務(wù),如果有則進(jìn)行執(zhí)行,具體實(shí)現(xiàn)代碼如下:
import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;
public class DelayQueueExample {
// zset key
private static final String _KEY = "myTaskQueue";
public static void main(String[] args) throws InterruptedException {
Jedis jedis = JedisUtils.getJedis();
// 30s 后執(zhí)行
long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
jedis.zadd(_KEY, delayTime, "order_1");
// 繼續(xù)添加測試數(shù)據(jù)
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");
// 開啟定時(shí)任務(wù)隊(duì)列
doDelayQueue(jedis);
}
/**
* 定時(shí)任務(wù)隊(duì)列消費(fèi)
* @param jedis Redis 客戶端
*/
public static void doDelayQueue(Jedis jedis) throws InterruptedException {
while (true) {
// 當(dāng)前時(shí)間
Instant nowInstant = Instant.now();
long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒時(shí)間
long nowSecond = nowInstant.getEpochSecond();
// 查詢當(dāng)前時(shí)間的所有任務(wù)
Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);
for (String item : data) {
// 消費(fèi)任務(wù)
System.out.println("消費(fèi):" + item);
}
// 刪除已經(jīng)執(zhí)行的任務(wù)
jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);
Thread.sleep(1000); // 每秒查詢一次
}
}
}② 鍵空間通知
我們可以通過 Redis 的鍵空間通知來實(shí)現(xiàn)定時(shí)任務(wù),它的實(shí)現(xiàn)思路是給所有的定時(shí)任務(wù)設(shè)置一個(gè)過期時(shí)間,等到了過期之后,我們通過訂閱過期消息就能感知到定時(shí)任務(wù)需要被執(zhí)行了,此時(shí)我們執(zhí)行定時(shí)任務(wù)即可。
默認(rèn)情況下 Redis 是不開啟鍵空間通知的,需要我們通過 config set notify-keyspace-events Ex 的命令手動開啟,開啟之后定時(shí)任務(wù)的代碼如下:
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();
// 執(zhí)行定時(shí)任務(wù)
doTask(jedis);
}
/**
* 訂閱過期消息,執(zhí)行定時(shí)任務(wù)
* @param jedis Redis 客戶端
*/
public static void doTask(Jedis jedis) {
// 訂閱過期消息
jedis.psubscribe(new JedisPubSub() {
@Override
public void onPMessage(String pattern, String channel, String message) {
// 接收到消息,執(zhí)行定時(shí)任務(wù)
System.out.println("收到消息:" + message);
}
}, _TOPIC);
}
}7、使用xxl-job實(shí)現(xiàn)定時(shí)任務(wù)
xxl-job是一款分布式定時(shí)任務(wù)調(diào)度平臺,可以實(shí)現(xiàn)各種類型的定時(shí)任務(wù)調(diào)度,如定時(shí)執(zhí)行Java代碼、調(diào)用HTTP接口、執(zhí)行Shell腳本等。xxl-job采用分布式架構(gòu),支持集群部署,可以滿足高并發(fā)、大數(shù)據(jù)量的任務(wù)調(diào)度需求。
三、開源項(xiàng)目
1、https://gitee.com/xuxueli0323/xxl-job
2、https://gitee.com/dromara/disjob
3、https://gitee.com/KFCFans/PowerJob
4、https://gitee.com/aizuda/snail-job
5、https://gitee.com/elasticjob/elastic-job
到此這篇關(guān)于Java中實(shí)現(xiàn)定時(shí)任務(wù)執(zhí)行的幾種常見方式總結(jié)的文章就介紹到這了,更多相關(guān)Java定時(shí)任務(wù)執(zhí)行內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中實(shí)現(xiàn)文件預(yù)覽的功能(實(shí)例代碼)
大家都知道word,Excel,PPT實(shí)現(xiàn)在線預(yù)覽常用的方式就是先轉(zhuǎn)換成pdf,然后在進(jìn)行預(yù)覽,下面給大家介紹Java中如何實(shí)現(xiàn)文件預(yù)覽的功能,需要的朋友可以參考下2023-05-05
Java采用setAsciiStream方法檢索數(shù)據(jù)庫指定內(nèi)容實(shí)例解析
這篇文章主要介紹了Java采用setAsciiStream方法檢索數(shù)據(jù)庫指定內(nèi)容,是比較實(shí)用的功能,需要的朋友可以參考下2014-08-08
Springboot整合redis實(shí)現(xiàn)發(fā)布訂閱功能介紹步驟
發(fā)布訂閱作為一種設(shè)計(jì)思想在很多開源組件中都有體現(xiàn),比如大家熟知的消息中間件等,可謂把發(fā)布訂閱這一思想體現(xiàn)的淋漓盡致了2022-09-09
Mybatis Mapper XML文件-插入,更新,刪除詳解(insert, updat
這篇文章主要介紹了MyBatis的Mapper XML文件中用于插入、更新和刪除數(shù)據(jù)的語句,包括這些語句的屬性和子元素的使用方法2025-02-02
SpringBoot實(shí)現(xiàn)大模型流式Streaming響應(yīng)的示例代碼
這篇文章主要為大家詳細(xì)介紹了SpringBoot實(shí)現(xiàn)大模型流式Streaming響應(yīng)的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2026-01-01
詳解Java七大阻塞隊(duì)列之SynchronousQueue
SynchronousQueue不需要存儲線程間交換的數(shù)據(jù),它的作用像是一個(gè)匹配器,使生產(chǎn)者和消費(fèi)者一一匹配。本文詳細(xì)講解了Java七大阻塞隊(duì)列之一SynchronousQueue,需要了解的小伙伴可以參考一下這篇文章2021-09-09

