浏览代码

fix cron bug

Looly 5 年之前
父节点
当前提交
8e42898d46

+ 3 - 0
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)

+ 50 - 10
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;
 	}
@@ -319,6 +352,13 @@ public class ThreadUtil {
 	}
 
 	/**
+	 * 等待当前线程结束. 调用 {@link Thread#join()} 并忽略 {@link InterruptedException}
+	 */
+	public static void waitForDie() {
+		waitForDie(Thread.currentThread());
+	}
+
+	/**
 	 * 等待线程结束. 调用 {@link Thread#join()} 并忽略 {@link InterruptedException}
 	 *
 	 * @param thread 线程

+ 27 - 7
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毫秒数,包括:
+	 * <pre>
+	 *     1. 是否&gt;0,防止用户向未来调整时间
+	 *     1. 是否&lt;两倍的间隔单位,防止用户向历史调整时间
+	 * </pre>
+	 *
+	 * @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);
+	}
 }

+ 4 - 3
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