任务调度线程池的使用
Timer
在任务调度线程池功能加入之前,可以使用 java.util.Timer
来实现定时功能,Timer
的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务
Timer的使用方法如下 :
package cn.knightzz.schedule;
import lombok.extern.slf4j.Slf4j;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("all")
@Slf4j(topic = "c.TimerTest")
public class TimerTest {
public static void main(String[] args) {
log.debug("start ... ");
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
log.debug("task1 ... ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
log.debug("task2 ... ");
}
};
// 使用 timer 添加两个任务,希望它们都在 1s 后执行
// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此『任务1』的延时,影响了『任务2』的执行
timer.schedule(task1, 1000);
timer.schedule(task2, 1000);
}
}
输出结果如下 :
15:23:08.666 [main] DEBUG c.TimerTest - start ...
15:23:09.673 [Timer-0] DEBUG c.TimerTest - task1 ...
15:23:11.686 [Timer-0] DEBUG c.TimerTest - task2 ...
初始时间是 = 08
正常情况下在 09
的时候 task1 和 task2 都要开始执行, 但是由于 Timer 内只有一个线程来顺序执行队列中的任务
所以 , 当上一个任务延时的情况下, 就会影响下一个任务的执行
任务调度线程池
schedule
我们可以使用 Executors
提供的带任务调度线程池对象来创建延时线程
@Test
public void scheduleTest() throws IOException {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
log.debug("start ... ");
// 添加2个任务, 让他们都在1s以后执行
scheduledThreadPool.schedule(() -> {
log.debug("task1 execute time {}" , new Date());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, 1, TimeUnit.SECONDS);
scheduledThreadPool.schedule(() -> {
log.debug("task2 execute time {}" , new Date());
}, 1, TimeUnit.SECONDS);
// 阻塞当前线程
System.in.read();
}
运行结果如下 :
15:41:56.161 [main] DEBUG c.ScheduledExecutorServiceTest - start ...
15:41:57.199 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:41:57 CST 2022
15:41:57.199 [pool-1-thread-2] DEBUG c.ScheduledExecutorServiceTest - task2 execute time Fri Sep 02 15:41:57 CST 2022
Process finished with exit code -1
可以看到两个任务是同一时间执行的, 并不会因为某个任务执行时间慢影响其他的任务
间隔调用
间隔执行 : 顾名思义, 每个一个周期执行一次任务 :
scheduleAtFixedRate(Runnable command , long initialDelay, long period, TimeUnit unit)
: initialDelay 延迟时间, period 周期时间
@Test
public void scheduleAtFixedRateTest() throws IOException {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
log.debug("start ... ");
// 每个1s执行一次
scheduledThreadPool.scheduleAtFixedRate(() -> {
log.debug("task1 execute time {}", new Date());
}, 1, 1, TimeUnit.SECONDS);
// 阻塞当前线程
System.in.read();
}
执行结果如下:
15:45:46.774 [main] DEBUG c.ScheduledExecutorServiceTest - start ...
15:45:47.819 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:47 CST 2022
15:45:48.810 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:48 CST 2022
15:45:49.816 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:49 CST 2022
15:45:50.823 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:50 CST 2022
15:45:51.818 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:51 CST 2022
15:45:52.809 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:52 CST 2022
15:45:53.808 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:53 CST 2022
15:45:54.815 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:54 CST 2022
15:45:55.819 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:45:55 CST 2022
Process finished with exit code -1
scheduleAtFixedRate 例子(任务执行时间超过了间隔时间):
@Test
public void scheduleAtFixedRateTest02() throws IOException {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
log.debug("start ... ");
// 每个1s执行一次
scheduledThreadPool.scheduleAtFixedRate(() -> {
log.debug("task1 execute time {}", new Date());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, 1, 1, TimeUnit.SECONDS);
// 阻塞当前线程
System.in.read();
}
当任务执行时间超过间隔时间, 那么在任务执行完以后就会立即执行下一个任务 :
15:53:51.998 [main] DEBUG c.ScheduledExecutorServiceTest - start ...
15:53:53.056 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:53:53 CST 2022
15:53:55.062 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:53:55 CST 2022
15:53:57.077 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:53:57 CST 2022
15:53:59.080 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:53:59 CST 2022
15:54:01.090 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:54:01 CST 2022
15:54:03.105 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:54:03 CST 2022
15:54:05.113 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:54:05 CST 2022
15:54:07.127 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:54:07 CST 2022
15:54:09.142 [pool-1-thread-1] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:54:09 CST 2022
15:54:11.155 [pool-1-thread-2] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:54:11 CST 2022
15:54:13.160 [pool-1-thread-2] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:54:13 CST 2022
15:54:15.170 [pool-1-thread-2] DEBUG c.ScheduledExecutorServiceTest - task1 execute time Fri Sep 02 15:54:15 CST 2022
Process finished with exit code -1
可以看到上面代码的运行结果, 输出分析:一开始,延时 1s,scheduleWithFixedDelay 的间隔是 上一个任务结束 <-> 延时 <-> 下一个任务开始
所
以间隔都是 3s
整个线程池表现为:线程数固定,任务数多于线程数时,会放入无界队列排队。任务执行完毕,这些线程也不会被释放。用来执行延迟或反复执行的任务
正确处理线程中的异常
主动捕捉异常
@Test
public void resolveException01() throws IOException {
// 主动捕捉异常
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> {
log.debug("task1 ...");
try {
int i = 1 / 0;
} catch (Exception e) {
log.error("error : {}" , e);
}
});
System.in.read();
}
16:19:07.682 [pool-1-thread-1] DEBUG c.ResolveExceptionTest - task1 ...
16:19:07.685 [pool-1-thread-1] ERROR c.ResolveExceptionTest - error : {}
java.lang.ArithmeticException: / by zero
at cn.knightzz.pool.exception.ResolveExceptionTest.lambda$resolveException01$0(ResolveExceptionTest.java:33)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
使用Future获取异常
@Test
public void resolveException02() throws IOException, ExecutionException, InterruptedException {
// 主动捕捉异常
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Boolean> future = pool.submit(() -> {
log.debug("task1 ...");
int i = 1 / 0;
return true;
});
log.debug("result : {}" , future.get());
System.in.read();
}
通过 future.get()
可以获取异常的结果
11:12:45.909 [pool-1-thread-1] DEBUG c.ResolveExceptionTest - task1 ...
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: 除以零
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
...
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.ArithmeticException: 除以零
at cn.knightzz.pool.exception.ResolveExceptionTest.lambda$resolveException02$1(ResolveExceptionTest.java:52)
at cn.knightzz.pool.exception.ResolveExceptionTest$$Lambda$1/0x0000000000819240.call(Unknown Source)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:823)
Process finished with exit code -1
线程池定时任务
定期执行
如何让每周四 18:00:00 定时执行任务?
package cn.knightzz.pool.schedule;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("all")
@Slf4j(topic = "c.ThreadPoolScheduleTest")
public class ThreadPoolScheduleTest {
public static void main(String[] args) {
// 获取当前的时间
LocalDateTime now = LocalDateTime.now();
// 获取本周四 18:00:00.00
LocalDateTime thursday = now.with(DayOfWeek.THURSDAY).withHour(18).withMinute(0).withSecond(0).withNano(0);
// 如果当前时间已经超过 本周四 18:00:00.000, 那么找下周四 18:00:00.000
if (now.compareTo(thursday) > 0) {
thursday = thursday.plusWeeks(1);
}
// 计算延迟执行的时间差
long initialDelay = Duration.between(now, thursday).toMillis();
// 计算时间间隔 : 1周的毫秒值
long oneWeek = 1000 * 3600 * 24 * 7 ;
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
log.debug("开始时间 : {}" , new Date());
executorService.scheduleAtFixedRate(() ->{
log.debug("开始执行时间 : {} " , new Date());
}, initialDelay, oneWeek, TimeUnit.MILLISECONDS);
}
}
基本思路 :
- 计算
initialDelay
: 计算当前时间和周四的时间差 :now.with(DayOfWeek.THURSDAY).withHour(18).withMinute(0).withSecond(0).withNano(0);
- 计算时间 :
1000 * 3600 * 24 * 7 ;