1.Java自带(Timer)

目录


Java定时任务调度专栏目录(点击进入…)


自带(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连续执行之间的时期
unitinitialDelay和period参数的时间单位
一个ScheduledFuture代表待完成的任务,其 get()方法将在取消时抛出异常

(4)ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,
long delay,TimeUnit unit)

创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。循环

参数描述
command要执行的任务
initialDelay延迟第一次执行的时间
delay一个执行终止与下一个执行的开始之间的延迟
unitinitialDelay和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目标平行度水平
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未禾

您的支持是我最宝贵的财富!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值