From 8e42898d4656f2efc9372b322e47b292a30536d8 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 19 Apr 2020 11:41:10 +0800 Subject: [PATCH] fix cron bug --- CHANGELOG.md | 3 + .../cn/hutool/core/thread/ThreadUtil.java | 60 +++++++++++++++---- .../main/java/cn/hutool/cron/CronTimer.java | 34 ++++++++--- .../java/cn/hutool/cron/demo/CronTest.java | 7 ++- 4 files changed, 84 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4d3832cc..8fe8c7ab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,11 @@ ### 新特性 * 【core 】 增加NetUtil.isOpen方法 +* 【core 】 增加ThreadUtil.sleep和safeSleep的重载 + ### Bug修复 * 【db 】 修复PageResult.isLast计算问题 +* 【cron 】 修复更改系统时间后CronTimer被阻塞的问题(issue#838@Github) ------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java index 379e4ff8d..1093d9bcd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java @@ -226,11 +226,23 @@ public class ThreadUtil { if (millis == null) { return true; } + return sleep(millis.longValue()); + } - try { - Thread.sleep(millis.longValue()); - } catch (InterruptedException e) { - return false; + /** + * 挂起当前线程 + * + * @param millis 挂起的毫秒数 + * @return 被中断返回false,否则true + * @since 5.3.2 + */ + public static boolean sleep(long millis) { + if (millis > 0) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + return false; + } } return true; } @@ -243,15 +255,36 @@ public class ThreadUtil { * @see ThreadUtil#sleep(Number) */ public static boolean safeSleep(Number millis) { - long millisLong = millis.longValue(); + if (millis == null) { + return true; + } + + return safeSleep(millis.longValue()); + } + + /** + * 考虑{@link Thread#sleep(long)}方法有可能时间不足给定毫秒数,此方法保证sleep时间不小于给定的毫秒数 + * + * @param millis 给定的sleep时间 + * @return 被中断返回false,否则true + * @see ThreadUtil#sleep(Number) + * @since 5.3.2 + */ + public static boolean safeSleep(long millis) { long done = 0; - while (done < millisLong) { - long before = System.currentTimeMillis(); - if (false == sleep(millisLong - done)) { + long before; + long spendTime; + while (done >= 0 && done < millis) { + before = System.currentTimeMillis(); + if (false == sleep(millis - done)) { return false; } - long after = System.currentTimeMillis(); - done += (after - before); + spendTime = System.currentTimeMillis() - before; + if (spendTime <= 0) { + // Sleep花费时间为0或者负数,说明系统时间被拨动 + break; + } + done += spendTime; } return true; } @@ -318,6 +351,13 @@ public class ThreadUtil { } } + /** + * 等待当前线程结束. 调用 {@link Thread#join()} 并忽略 {@link InterruptedException} + */ + public static void waitForDie() { + waitForDie(Thread.currentThread()); + } + /** * 等待线程结束. 调用 {@link Thread#join()} 并忽略 {@link InterruptedException} * diff --git a/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java b/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java index 81a5ea27d..84d7317a2 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java @@ -44,16 +44,18 @@ public class CronTimer extends Thread implements Serializable { long sleep; while(false == isStop){ //下一时间计算是按照上一个执行点开始时间计算的 + //此处除以定时单位是为了清零单位以下部分,例如单位是分则秒和毫秒清零 nextTime = ((thisTime / timerUnit) + 1) * timerUnit; sleep = nextTime - System.currentTimeMillis(); - if (sleep > 0 && false == ThreadUtil.safeSleep(sleep)) { - //等待直到下一个时间点,如果被中断直接退出Timer - break; + if(isValidSleepMillis(sleep, timerUnit)){ + if (false == ThreadUtil.safeSleep(sleep)) { + //等待直到下一个时间点,如果被中断直接退出Timer + break; + } + //执行点,时间记录为执行开始的时间,而非结束时间 + thisTime = System.currentTimeMillis(); + spawnLauncher(thisTime); } - - //执行点,时间记录为执行开始的时间,而非结束时间 - thisTime = System.currentTimeMillis(); - spawnLauncher(thisTime); } log.debug("Hutool-cron timer stoped."); } @@ -73,4 +75,22 @@ public class CronTimer extends Thread implements Serializable { private void spawnLauncher(final long millis){ this.scheduler.taskLauncherManager.spawnLauncher(millis); } + + /** + * 检查是否为有效的sleep毫秒数,包括: + *
+	 *     1. 是否>0,防止用户向未来调整时间
+	 *     1. 是否<两倍的间隔单位,防止用户向历史调整时间
+	 * 
+ * + * @param millis 毫秒数 + * @param timerUnit 定时单位,为秒或者分的毫秒值 + * @return 是否为有效的sleep毫秒数 + * @since 5.3.2 + */ + private static boolean isValidSleepMillis(long millis, long timerUnit){ + return millis > 0 && + // 防止用户向前调整时间导致的长时间sleep + millis < (2 * timerUnit); + } } diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java index 72ad80c2a..76f0dad19 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java @@ -36,13 +36,14 @@ public class CronTest { } @Test - @Ignore +// @Ignore public void cronTest2() { // 支持秒级别定时任务 CronUtil.setMatchSecond(true); CronUtil.start(); - - ThreadUtil.sleep(30000); + + ThreadUtil.waitForDie(); + Console.log("Exit."); } @Test