目录
自带(Timer)优缺点
类型 | 描述 |
---|---|
优点 | JDK本身就自带该工具类,无需第三方依赖,只需实现TimerTask类即可使用Timer进行调度配置,使用起来简单方便 |
缺点 | Timer中所有的任务都一个TaskThread线程来调度和执行,任务的执行方式是串行的,如果前一个任务发生延迟或异常会影响到后续任务的执行 |
Timer和TimerTask
Timer是JDK自带的任务调度工具类,只需要java.util.Timer和java.util.TimerTask两个类就可以实现基本任务调度功能
Timer是一种线程设施,用于安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行,可以看成一个定时器,可以调度TimerTask。TimerTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力
定时器就像使用闹钟一样,可以在固定的时间做某件事,也可以在固定的时间段重复做某件事
(1)Timer
线程调度任务以供将来在后台线程中执行的功能。任务可以安排一次执行,或定期重复执行
对应于每个Timer对象是单个后台线程,用于依次执行所有定时器的所有任务。计时器任务应该快速完成。如果一个定时器任务需要花费很多时间来完成,它会“计时”执行线程。 这可能会延迟随后的任务的执行,这些任务在(和)如果违规任务最后完成时,可能会“束起来”并快速执行
在最后一次对Timer对象的引用后,所有未完成的任务已完成执行,定时器的任务执行线程正常终止(并被收集到垃圾回收)。 但是,这可能需要任意长时间的发生
默认情况下,任务执行线程不作为守护程序线程运行,因此它能够使应用程序终止。如果想要快速终止定时器的任务执行线程,则调用者应该调用定时器的cancel方法
如果定时器的任务执行线程意外终止,例如:调用了stop方法,那么在计时器上安排任务的任何进一步的尝试将会产生一个IllegalStateException ,就像定时器的cancel方法被调用一样。
线程安全:多个线程可以共享一个单独的Timer对象,而不需要外部同步
不提供实时保证:使用Object.wait(long)方法是调度任务。
构造方法
构造方法 | 描述 |
---|---|
Timer() | 创建一个新的计时器 |
Timer(boolean isDaemon) | 创建一个新的定时器,其相关线程可以指定为run as a daemon(运行为守护线程) |
Timer(String name) | 创建一个新的定时器,其相关线程具有指定的名称 |
Timer(String name, boolean isDaemon) | 创建一个新的定时器,其相关线程具有指定的名称,可以指定为 run as a daemon |
1.Timer()
创建一个新的计时器。相关联的线程不是作为守护进程运行(run as a daemon)
2.Timer(boolean isDaemon)
创建一个新的定时器,其相关线程可以指定为守护线程
如果定时器将用于调度重复的“维护活动”,只要应用程序正在运行,但是不应该延长应用程序的使用寿命,则会调用守护线程
参数 | 描述 |
---|---|
isDaemon | 如果关联的线程应该作为守护进程运行,则为true |
3.Timer(String name)
创建一个新的定时器,其相关线程具有指定的名称。相关的线程不是守护线程
参数 | 描述 |
---|---|
name | 相关线程的名称 |
如果name为空则会抛出NullPointerException异常
4.Timer(String name,boolean isDaemon)
创建一个新的定时器,其相关线程具有指定的名称,可以指定其为守护线程
参数 | 描述 |
---|---|
name | 相关线程的名称 |
isDaemon | 如果关联的线程应该作为守护程序运行,则为true |
如果name为空则会抛出NullPointerException异常
实例方法
方法 | 描述 |
---|---|
void cancel() | 终止此计时器,丢弃任何当前计划的任务 |
int purge() | 从该计时器的任务队列中删除所有取消的任务 |
schedule(重点)
方法 | 描述 |
---|---|
void schedule(TimerTask task, Date time) | 在指定的时间安排指定的任务执行 |
void schedule(TimerTask task, Date firstTime, long period) | 从指定的时间开始,对指定的任务执行重复的固定延迟执行 |
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 从指定的时间开始,对指定的任务执行重复的固定速率执行 |
异常:
IllegalArgumentException - 如果是 firstTime.getTime() < 0或 period <= 0
IllegalStateException - 如果任务已经被调度或取消,定时器被取消或定时器线程被终止。
NullPointerException - 如果 task或 firstTime为空
方法 | 描述 |
---|---|
void schedule(TimerTask task, long delay) | 在指定的延迟之后安排指定的任务执行 |
void schedule(TimerTask task, long delay, long period) | 在指定的延迟之后开始,重新执行固定延迟执行的指定任务 |
void scheduleAtFixedRate(TimerTask task, long delay, long period) | 在指定的延迟之后开始,重新执行固定速率的指定任务 |
异常:
IllegalArgumentException - 如果delay < 0或 delay + System.currentTimeMillis() < 0或period <= 0
IllegalStateException - 如果任务已经被调度或取消,定时器被取消或定时器线程被终止。
NullPointerException - 如果 task为空
参数
参数 | 描述 |
---|---|
第一个参数 | 要安排的任务;继承TimerTask类,并实现public void run()方法,TimerTask类实现了Runnable接口 |
第二个参数 | 制定timer定时器第一次调用run方法的时间;0表示不指时间(无延迟),立刻调用。一般这个参数指定的时间格式是:yyyy-MM-dd HH:mm:ss ;long:执行任务之前以delay为单位的延迟(毫秒);Date:第一次执行任务的时间 |
第三个参数 | 连续执行任务之间的相隔时间;第一次调用之后,从第二次开始每隔多长的时间调用一次run()方法。单位:毫秒;60601000为一小时、3601000为三分钟 |
(2)TimerTask
可以由计时器进行一次性或重复执行的任务
public abstract class TimerTask
implements Runnable {}
实现了Runnable接口的抽象类,开发人员只需要继承TimerTask并实现run方法,将任务内容写入run方法中,然后就可以将其交给Timer去调度执行
构造方法
构造方法 | 描述 |
---|---|
protected TimerTask() | 创建一个新的计时器任务 |
方法
方法 | 描述 |
---|---|
boolean cancel() | 取消此计时器任务 |
abstract void run() | 该定时器任务要执行的操作 |
long scheduledExecutionTime() | 返回此任务最近实际执行的预定执行时间 |
执行原理
Timer类的核心是它的两个内部类TaskThread和TaskQueue
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
在Timer创建的同时会初始化TaskThread和TaskQueue对象,并调用TaskThread的start方法启动该线程。Timer通过上述的schedule方法会将需要调度的TimerTask放入TaskQueue中,每次往TaskQueue放入新的TimerTask时,TaskQueue会按照任务的执行时间由小到大进行排序
Timer的设计核心是一个TaskList和一个TaskThread
Timer将接收到的任务丢到自己的 TaskList 中,TaskList 按照Task的最初执行时间进行排序。TimerThread在创建Timer时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread被唤醒并执行该任务。之后TimerThread更新最近一个要执行的任务,继续休眠
TimerThread线程在start方法启动后,就会开始不断轮询,每次轮询都会获取TaskQueue中第一个TimerTask( 执行时间最小的TimerTask),判断当前是否已到执行时间:
(1)如当前时间大于或等于执行时间,则执行TimerTask;如未到,则会休眠一段时间(时长=任务执行时间-当前时间)
(2)执行后,判定该task是否需要重复执行,如需要,则重置该task的执行时间,重新放入TaskQueue中(放入后会自动排序)
创建一个Timer对象,把需要执行的任务封装到一个TimeTask对象里,添加到Timer的任务列表里(TaskQueue,一个TimeTask数组)。因为Timer里有一个成员变量TimerThreed,一个线程类,Timer在创建的时候就会使此线程运行,TimerThreed里一个死循环的代码段,此代码段不断的检查任务数组中是否有需要执行的任务,如果有就执行任务的run方法来完成任务的调用
因为此实现是单线程负责任务的调度,所有会出现任务阻塞,延迟,甚至一个任务异常会影响全
Timer和TimerTask简单的任务调度
1.其中Timer负责设定TimerTask的起始时间和间隔执行时间。只需要继承TimerTask,实现run()方法,然后将其交给Timer去定时执行
2.Timer的设计核心是一个TaskList和一个TaskThread。Timer将接收到的任务压入TaskList,任务执行排序是按照最初的执行时间排序的
3.TimerThread在创建Timer时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread被唤醒并执行该任务。之后TimerThread更新最近一个要执行的任务,继续休眠
4.Timer的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务
public class OneTask extends TimerTask {
private int id;
public OneTask(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println("线程" + id + ": 正在 执行。。");
// System.gc();
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new OneTask(1), 5000); // 5秒后启动任务
OneTask secondTask = new OneTask(2);
// 1秒后启动任务,以后每隔3秒执行一次线程
timer.schedule(secondTask, 1000, 3000);
Date date = new Date();
// 以date为参数,指定某个时间点执行线程
timer.schedule(new OneTask(3), new Date(date.getTime() + 1000));
// timer.cancel();
// secondTask.cancel();
System.out.println("end in main thread...");
}
}
ScheduledExecutorService
public interface ScheduledExecutorService
extends ExecutorService {}
Timer的内部只有一个线程,如果有多个任务的话就会顺序执行,这样延迟时间和循环时间就会出现问题(java.util.concurrent.ScheduledExecutorService)
ScheduledExecutorService是线程池,所以就不会出现这个情况,在对延迟任务和循环任务要求严格的时候,就需要考虑使用ScheduledExecutorService
ExecutorService可以调度命令在给定的延迟之后运行,或定期执行。schedule方法创建具有各种延迟的任务,并返回可用于取消或检查执行的任务对象
scheduleAtFixedRate和scheduleWithFixedDelay方法创建并执行定期运行的任务,直到取消
使用Executor.execute(Runnable)和ExecutorService submit方法提交的命令以请求的延迟为零进行调度。在schedule方法中也允许零和负延迟(但不是周期),并被视为立即执行的请求。
所有schedule方法接受相对延迟和句点作为参数,而不是绝对时间或日期。 这是一个简单的事情变换表示为绝对时间Date至所需的形式。 例如,要在一定的未来date ,您可以使用: schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
注意:由于网络时间同步协议,时钟漂移或其他因素,相对延迟的到期不需要与任务启用的当前Date重合
Executors类为此包中提供的ScheduledExecutorService实现提供了方便的工厂方法
Java 5.0引入了java.util.concurrent软件包
其中一个java.util.concurrent程序是ScheduledThreadPoolExecutor ,它是用于以给定速率或延迟重复执行任务的线程池。 这实际上是对一个更灵活的替代Timer / TimerTask组合,因为它允许多个服务线程,接受各种时间单位,并且不需要子类TimerTask (只实现Runnable )。 使用一个线程配置ScheduledThreadPoolExecutor使其等同于Timer
注意:这个类可以扩展到大量并发计划任务(千应该没有问题)。 在内部,它使用二进制堆表示其任务队列,因此计划任务的成本为O(log n),其中n为并发计划任务的数量
实现注意事项:所有构造函数启动计时器线程
ScheduledExecutorService是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
注意:只有当调度任务来的时候,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是处于轮询任务的状态
(1)ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit)
创建并执行在给定延迟后启用的单次操作
参数 | 描述 |
---|---|
command | 要执行的任务 |
delay | 从现在开始延迟执行的时间 |
unit | 延时参数的时间单位 |
表示任务等待完成,并且其的ScheduledFuture get()方法将返回 null完成后 |
(2) ScheduledFuture schedule(Callable callable,long delay,TimeUnit unit)
创建并执行在给定延迟后启用的ScheduledFuture。单次操作
参数 | 描述 |
---|---|
V | 可调用结果的类型 |
callable | 执行的功能 |
delay | 从现在开始延迟执行的时间 |
unit | 延迟参数的时间单位 |
一个可用于提取结果或取消的ScheduledFuture |
(3)ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,
long period,TimeUnit unit)
创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作;那就是执行将在initialDelay之后开始,然后initialDelay+period,然后是initialDelay + 2 * period等等。如果任务的执行遇到异常,则后续的执行被抑制。否则,任务将仅通过取消或终止执行人终止。如果任务执行时间比其周期长,则后续执行可能会迟到,但不会同时执行。循环
参数 | 描述 |
---|---|
command | 要执行的任务 |
initialDelay | 延迟第一次执行的时间 |
period | 连续执行之间的时期 |
unit | initialDelay和period参数的时间单位 |
一个ScheduledFuture代表待完成的任务,其 get()方法将在取消时抛出异常 |
(4)ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,
long delay,TimeUnit unit)
创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。循环
参数 | 描述 |
---|---|
command | 要执行的任务 |
initialDelay | 延迟第一次执行的时间 |
delay | 一个执行终止与下一个执行的开始之间的延迟 |
unit | initialDelay和delay参数的时间单位 |
一个ScheduledFuture代表待完成的任务,其 get()方法将在取消时抛出异常 |
ScheduleAtFixedRate和ScheduleWithFixedDelay区别
(1)ScheduleAtFixedRate
每次执行时间为上一次任务开始起向后推一个时间间隔
每次执行时间:initialDelay,initialDelay+period,initialDelay+2*period…
(2)ScheduleWithFixedDelay
每次执行时间为上一次任务结束起向后推一个时间间隔
每次执行时间:
initialDelay,initialDelay+executeTime+delay,initialDelay+2executeTime+2delay…
由此可见,ScheduleAtFixedRate是基于固定时间间隔进行任务调度,ScheduleWithFixedDelay取决于每次任务执行的时间长短,是基于不固定时间间隔进行任务调度
每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰
需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间ScheduledExecutor都是在轮询任务的状态
ScheduleAtFixeRate任务调度
1.使用ScheduleAtFixedRate
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
// 设置日期格式
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("thread:" + df.format(new Date()));
}
}, 2, 3, TimeUnit.SECONDS);// 在2s后,子线程开始执行,并且每过3s轮询执行一次
//最先执行
System.out.println("main:" + df.format(new Date()));
2.使用ScheduleWithFixedDelay
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
// 设置日期格式
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("thread:" + df.format(new Date()));
}
}, 2, 3, TimeUnit.SECONDS);
3.使用ScheduledExecutor + Calendar
用ScheduledExecutor和Calendar实现复杂任务调度
Timer和ScheduledExecutor都仅能提供基于开始时间与重复间隔的任务调度,不能胜任更加复杂的调度需求
比如:设置每星期二的16:38:10执行任务。该功能使用Timer和ScheduledExecutor都不能直接实现,但可以借助Calendar间接实现该功能
public class ExecutorTest {
private static final long DAY_TIMES = 1000;
public static void scheduleAtFixedRate(Runnable runnable, int day, int hour, int min) throws Exception {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
long curLong = System.currentTimeMillis();
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(curLong);
calendar.add(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, min);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
long delay = calendar.getTimeInMillis() - curLong;
System.out.println(new Date(delay));
executor.scheduleAtFixedRate(runnable, delay, DAY_TIMES, TimeUnit.MILLISECONDS);
}
public static void main(String[] args) {
Exec manage = new Exec();
try {
scheduleAtFixedRate(manage, 0, 17, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Exec implements Runnable {
static int cnt = 1;
boolean taskstatus = false;
@Override
public void run() {
taskstatus = true;
}
public boolean isTaskstatus() {
return taskstatus;
}
}
Executor(超级接口)
Executor
Java线程池的超级接口;提供一个execute(Runnable command)方法;一般用它的继承接口ExecutorService
Executors
java.util.concurrent包下的一个类,提供了若干个静态方法,用于生成不同类型的线程池
ExecutorService
线程池定义的一个接口,继承Executor。有两个实现类,分别为ThreadPoolExecutor、ScheduledThreadPoolExecutor
Executors下类方法(静态方法)
1.newScheduledThreadPool
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个定长线程池,支持定时及周期性任务执行
参数 | 描述 |
---|---|
corePoolSize | 要保留在池中的线程数,即使它们处于空闲状态 |
ScheduledExecutorService newScheduledThreadPool(int corePoolSize,ThreadFactory threadFactory)
创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行
参数 | 描述 |
---|---|
corePoolSize | 要保留在池中的线程数,即使它们处于空闲状态 |
threadFactory | 执行程序创建新线程时使用的工厂 |
2.newFixedThreadPool
static ExecutorService newFixedThreadPool(int nThreads)
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
在任何时候,最多nThreads线程将处于主动处理任务。 如果所有线程处于活动状态时都会提交其他任务,则它们将等待队列中直到线程可用。 如果任何线程由于在关闭之前的执行期间发生故障而终止,则如果需要执行后续任务,则新线程将占用它。 池中的线程将存在,直到它明确地为shutdown(关闭)
参数 | 描述 |
---|---|
nThreads | 池中的线程数 |
static ExecutorService newFixedThreadPool(int nThreads,ThreadFactory threadFactory)
创建一个线程池,重用固定数量的线程,从共享无界队列中运行,使用提供的ThreadFactory在需要时创建新线程。 在任何时候,最多nThreads个线程将处于主动处理任务。 如果所有线程处于活动状态时都会提交其他任务,则它们将等待队列中直到线程可用。 如果任何线程由于在关闭之前的执行期间发生故障而终止,则如果需要执行后续任务,则新线程将占用它。 池中的线程将存在,直到它明确地为shutdown
参数 | 描述 |
---|---|
nThreads | 池中的线程数 |
threadFactory | 工厂在创建新线程时使用 |
3.newSingleThreadExecutor
static ExecutorService newSingleThreadExecutor()
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO。优先级)执行
创建一个使用从无界队列运行的单个工作线程的执行程序
注意:如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,则如果需要执行后续任务,则新的线程将占用它
任务保证顺序执行,并且不超过一个任务将被激活在任何给定的时间。与其他等效的newFixedThreadPool(1) newFixedThreadPool(1),返回的执行器保证不被重新配置以使用额外的线程。
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
创建一个使用单个工作线程运行无界队列的执行程序,并在需要时使用提供的ThreadFactory创建一个新线程。 与其他等效的newFixedThreadPool(1, threadFactory) newFixedThreadPool(1, threadFactory) ,返回的执行器保证不被重新配置以使用额外的线程
参数 | 描述 |
---|---|
threadFactory | 工厂在创建新线程时使用 |
4.newSingleThreadScheduledExecutor
static ScheduledExecutorService newSingleThreadScheduledExecutor()
创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行
注意:如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,则如果需要执行后续任务,则新的线程将占用它
任务保证顺序执行,并且不超过一个任务将被激活在任何给定的时间。 与其他等效的newScheduledThreadPool(1) newScheduledThreadPool(1) ,返回的执行器保证不被重新配置以使用额外的线程
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行
(请注意,如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,则如果需要执行后续任务,则新的线程将占用它。)任务保证顺序执行,并且不超过一个任务将被激活在任何给定的时间。 与其他等效的newScheduledThreadPool(1, threadFactory) newScheduledThreadPool(1, threadFactory) ,返回的执行器保证不被重新配置以使用额外的线程
参数 | 描述 |
---|---|
threadFactory | 创建新线程时使用的工厂 |
5.newCachedThreadPool
static ExecutorService newCachedThreadPool()
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制)
创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。这些池通常会提高执行许多短暂异步任务的程序的性能。调用execute将重用以前构造的线程(如果可用)。 如果没有可用的线程,将创建一个新的线程并将其添加到该池中。 未使用六十秒的线程将被终止并从缓存中删除。因此,长时间保持闲置的池将不会消耗任何资源。 请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但不同详细信息的池(例如,超时参数)
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程
参数 | 描述 |
---|---|
threadFactory | 创建新线程时使用的工厂 |
6.newWorkStealingPool(JDK 1.8)
static ExecutorService newWorkStealingPool()
jdk1.8新增,创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争,它需要穿一个并行级别的参数,如果不传,则被设定为默认的CPU数量
static ExecutorService newWorkStealingPool(int parallelism)
创建一个维护足够的线程以支持给定的并行级别的线程池,并且可以使用多个队列来减少争用。 并行级别对应于主动参与或可以从事任务处理的最大线程数。 线程的实际数量可以动态增长和收缩。 工作窃取池不保证执行提交的任务的顺序
参数 | 描述 |
---|---|
parallelism | 目标平行度水平 |