java计时器和死循环哪个好?哪个建议使用?
- 计时器性能更好,但是写起来稍微复杂一点。如果是非常短暂的延迟,用死循环也未尝不可。
- 一般来说能不用死循环的尽量不用死循环!
- 如果你使用的是JDK1.5以上的,可以使用ScheduledThreadPoolExecutor来完成Timer的功能,这个实现起来简单,而且相比timer的缺陷进行了弥补。
- 那就看你程序对时间的要求高不高,循环内处理时间长不长了。用死循环的话,它执行的时间不是固定的(每次执行完一次任务时间+睡眠时间)用计时器的话,它是每隔固定时间执行一次。
死循环实现定时器
//死循环方式, 无法保证一直执行, 曾经在Spring Boot上使用这种方式执行定时任务, 运行两个月后不知道哪天这个任务
//所在的线程不执行了, 这条线程的所有异常都catch了, 没有往外抛.
while(true){
Thread.sleep(time);
code;
}
Timer
Timer类提供了一个核心接口,schedule(安排) 指定一个任务交给定时器,在一定时间之后再去执行这个任务~
使用Timer和TimerTask存在一些缺陷:
1.Timer只创建了一个线程。当你的任务执行的时间超过设置的延时时间将会产生一些问题。
2.Timer创建的线程没有处理异常,因此一旦抛出非受检异常,该线程会立即终止。
JDK 5.0以后推荐使用ScheduledThreadPoolExecutor。该类属于Executor Framework,它除了能处理异常外,还可以创建多个线程解决上面的问题。
//Timer和TimerTask的使用
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
log.e("time:");
}
}, 2000, 40);//2000表示第一次执行任务延迟时间,40表示以后每隔多长时间执行一次run里面的任务
//取消定时器
timer.cancel();
ScheduledThreadPoolExecutor (强烈建议使用)
ScheduledThreadPoolExecutor,它可另行安排在给定的延迟后运行命令,或者定期执行命令。需要多个辅助线程时,或者要求 ThreadPoolExecutor 具有额外的灵活性或功能时,此类要优于Timer。
参考:
https://blog.csdn.net/cristianoxm/article/details/107640772
https://blog.csdn.net/wenzhi20102321/article/details/78681379
注意:
如果ScheduledThreadPoolExecutor中执行的任务出错抛出异常后,不仅不会打印异常堆栈信息,同时还会取消后面的调度。
https://blog.csdn.net/m0_71777195/article/details/127441811
//ScheduledThreadPoolExecutor的简单使用, 这里先学会简单使用再深入探讨。
//corePoolSize – 保留在池中的线程数,即使它们处于空闲状态,除非设置allowCoreThreadTimeOut
ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2);
//固定频率周期任务scheduleAtFixedRate, 除了这个类型的还有三种类型任务.
scheduled.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(LocalDateTime.now());
try {
Thread.sleep(8000);
} catch (Exception e) {
//TODO 不要往外面抛异常, ScheduledThreadPoolExecutor中执行的任务出错抛出异常后,不仅不会打印异常堆栈信息,同时还会取消后面的调度.
e.printStackTrace();
}
}
}, 0, 40, TimeUnit.MILLISECONDS);
//0表示首次执行任务的延迟时间,40表示每次执行任务的间隔时间,TimeUnit.MILLISECONDS执行的时间间隔数值单位
//间隔单位毫秒:TimeUnit.MILLISECONDS
//间隔单位秒:TimeUnit.SECONDS
//间隔单位分钟:TimeUnit.MINUTES
//间隔单位小时:TimeUnit.HOURS
//间隔单位天:TimeUnit.DAYS
//输出结果:
2022-11-29T21:54:22.090508100
2022-11-29T21:54:30.096814100
2022-11-29T21:54:38.102857400
2022-11-29T21:54:46.105952300
//这个方法的作用是周期性的调度task执行。task第一次执行的延迟根据initialDelay参数确定,以后每一次执行都间隔period时长。
//如果task的执行时间大于定义的period,那么下一个线程将在当前线程完成之后再执行。整个调度保证不会出现一个以上任务同时执行。
//尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。从此方法返回后,这些任务将从任务队列中排出(删除)。
//此方法不等待主动执行的任务终止。使用awaitTermination来做到这一点。
//除了尽最大努力停止处理正在执行的任务之外,没有任何保证。此实现通过Thread.interrupt中断任务;任何未能响应中断的任务可能永远不会终止。
scheduled.shutdownNow();
ScheduledThreadPoolExecutor 结构概述
ScheduledExecutorService接口介绍
ScheduledThreadPoolExecutor直接继承自ScheduledExecutorServiceScheduledThreadPoolExecutor 类的功能也主要体现在ScheduledExecutorService 接口上,它的最主要的功能就是可以对其中的任务进行调度,比如延迟执行、定时执行等等。
ScheduledExecutorService接口定义:
public interface ScheduledExecutorService extends ExecutorService {
//提交在给定延迟后启用的一次性任务(无返回值)。
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
//提交在给定延迟后启用带返回值的一次性任务。
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
//固定频率周期任务
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
//固定延迟周期任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
}
从上面接口定义我们知道,提供了四个方法,下面我们就分别介绍:
- schedule (Runnable task, long delay, TimeUnit timeunit) -> 一次性任务(无返回值)
这个方法的意思是在指定延迟之后运行task。这个方法有个问题,就是没有办法获知task的执行结果。如果我们想获得task的执行结果,我们可以传入一个Callable的实例.
//创建一个线程池,可以安排命令在给定的延迟后运行,或定期执行
//corePoolSize保留在池中的线程数,即使它们处于空闲状态
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
ScheduledFuture scheduledFuture =
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("Executed!");
}
},5,TimeUnit.SECONDS);
//有序关闭之前提交的任务,但不会接受新的任务。如果已经关闭,调用没有额外的效果。
//此方法不等待先前提交的任务执行完成, 如果要等待任务执行完成后再关闭使用awaitTermination方法.
scheduledExecutorService.shutdown();
- schedule (Callable task, long delay, TimeUnit timeunit) -> 一次性任务(有返回值)
这个方法与schedule (Runnable task)类似,也是在指定延迟之后运行task,不过它接收的是一个Callable实例,此方法会返回一个ScheduleFuture对象,通过ScheduleFuture我们可以取消一个未执行的task,也可以获得这个task的执行结果。
//创建一个线程池,可以安排命令在给定的延迟后运行,或定期执行
//corePoolSize保留在池中的线程数,即使它们处于空闲状态
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
ScheduledFuture scheduledFuture =
scheduledExecutorService.schedule(new Callable() {
public Object call() {
System.out.println("Executed!");
return "Called!";
}
},5,TimeUnit.SECONDS);
System.out.println("result = " + scheduledFuture.get());
//有序关闭之前提交的任务,但不会接受新的任务。如果已经关闭,调用没有额外的效果。
//此方法不等待先前提交的任务执行完成, 如果要等待任务执行完成后再关闭使用awaitTermination方法.
scheduledExecutorService.shutdown();
- scheduleAtFixedRate (Runnable, long initialDelay, long period,TimeUnit timeunit) -> 周期性任务
这个方法的作用是周期性的调度task执行。task第一次执行的延迟根据initialDelay参数确定,以后每一次执行都间隔period时长。
如果task的执行时间大于定义的period,那么下一个线程将在当前线程完成之后再执行。整个调度保证不会出现一个以上任务同时执行。
//corePoolSize – 保留在池中的线程数,即使它们处于空闲状态,除非设置allowCoreThreadTimeOut
ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2);
//固定频率周期任务scheduleAtFixedRate, 除了这个类型的还有三种类型任务.
scheduled.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(LocalDateTime.now());
try {
Thread.sleep(8000);
} catch (Exception e) {
//TODO 不要往外面抛异常, ScheduledThreadPoolExecutor中执行的任务出错抛出异常后,不仅不会打印异常堆栈信息,同时还会取消后面的调度.
e.printStackTrace();
}
}
}, 0, 40, TimeUnit.MILLISECONDS);
//0表示首次执行任务的延迟时间,40表示每次执行任务的间隔时间,TimeUnit.MILLISECONDS执行的时间间隔数值单位
//间隔单位毫秒:TimeUnit.MILLISECONDS
//间隔单位秒:TimeUnit.SECONDS
//间隔单位分钟:TimeUnit.MINUTES
//间隔单位小时:TimeUnit.HOURS
//间隔单位天:TimeUnit.DAYS
//输出结果:
2022-11-29T21:54:22.090508100
2022-11-29T21:54:30.096814100
2022-11-29T21:54:38.102857400
2022-11-29T21:54:46.105952300
//尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。从此方法返回后,这些任务将从任务队列中排出(删除)。
//此方法不等待主动执行的任务终止。使用awaitTermination来做到这一点。
//除了尽最大努力停止处理正在执行的任务之外,没有任何保证。此实现通过Thread.interrupt中断任务;任何未能响应中断的任务可能永远不会终止。
scheduled.shutdownNow();
- scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit) -> 周期性任务
scheduleWithFixedDelay的参数和scheduleAtFixedRate参数完全一致,它们的不同之处在于对period调度周期的解释。在scheduleAtFixedRate中,period指的两个任务开始执行的时间间隔,也就是当前任务的开始执行时间和下个任务的开始执行时间之间的间隔。而在scheduleWithFixedDelay中,period指的当前任务的结束执行时间到下个任务的开始执行时间。
ScheduledExecutorService的关闭
和ExecutorService类似, 我们在使用完ScheduledExecutorService时需要关闭它。如果不关闭的话,JVM会一直运行直,即使所有线程已经关闭了。关闭ScheduledExecutorService可以使用其继承自ExecutorService接口的shutdown()和shutdownNow()方法,两者的区别请参考【Java线程池 ExecutorService】。