`
victorzhzh
  • 浏览: 203014 次
  • 来自: ...
社区版块
存档分类
最新评论

定时且周期性的任务研究I--Timer

阅读更多

很多时候我们希望任务可以定时的周期性的执行,在最初的JAVA工具类库中,通过Timer可以实现定时的周期性的需求,但是有一定的缺陷,例如:Timer是基于绝对时间的而非支持相对时间,因此Timer对系统时钟比较敏感。虽然有一定的问题,但是我们还是从这个最简单的实现开始研究。

 

首先,我们准备一些讨论问题的类:TimerTask1和TimerLongTask,如下

public class TimerTask1 extends TimerTask {

	@Override
	public void run() {
		String base = "abcdefghijklmnopqrstuvwxyz0123456789";
		Random random = new Random();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < 10; i++) {
			int number = random.nextInt(base.length());
			sb.append(base.charAt(number));
		}
		System.out.println(new Date()+","+sb.toString());
	}

}

 这个类负责生成一个含有10个字符的字符串,这里我们将输出时间打印出来,近似认为是任务执行的时间。

public class TimerLongTask extends TimerTask {

	@Override
	public void run() {
		System.out.println("TimerLongTask: 开始沉睡");
		try {
			TimeUnit.SECONDS.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("TimerLongTask: 已经醒来");
	}

}

 这个类启动了一个长任务,即让任务沉睡10秒。

 

下面我们来看一个定时任务执行的例子:

	public static void main(String[] args) throws InterruptedException {
		Timer timer = new Timer();
		timer.schedule(new TimerTask1(), 1);
		timer.schedule(new TimerLongTask(), 1);
		timer.schedule(new TimerTask1(), 2);
		TimeUnit.SECONDS.sleep(20);
		timer.cancel();
	}

 在这个例子中,我们先提交了一个TimerTask1任务,且让它延迟1毫秒执行,紧接着我们又提交了一个TimerLongTask长任务,且让它也延迟1毫秒执行,最后我们在提交一个TimerTask1任务,延迟2毫秒执行。然后让主线程沉睡20秒后关闭timer。我们看一下执行结果:

Thu Apr 21 11:04:31 CST 2011,utg3hn7u4r
TimerLongTask: 开始沉睡
TimerLongTask: 已经醒来
Thu Apr 21 11:04:41 CST 2011,4aac22sud1

 这里我们看到第一次输出10个字符的时间和第二次输出10个字符的时间上相差了10秒,这10秒恰恰是长任务沉睡的时间,通过这个输出我们可以分析出:Timer用来执行任务的线程其实只有一个,且逐一被执行。接下来我们查看一下源码验证一下,如下:

private TaskQueue queue = new TaskQueue();
private TimerThread thread = new TimerThread(queue);

 这两行代码来自Timer源码,我们可以看到在第一次创建了Timer时就已经创建了一个thread和一个queue,因此只有一个线程来执行我们的任务。

那么Timer是如何来执行任务的?

首先我们调用timer.schedule方法,将任务提交到timer中,Timer中有很多重载的schedule方法,但它们都会调用同一个方法即sched方法。这个方法会将我们提交的任务添加到TaskQueue的队列中(即queue),在每次添加时都会根据nextExecutionTime大小来调整队列中任务的顺序,让nextExecutionTime最小的排在队列的最前端,nextExecutionTime最大的排在队列的最后端。在创建Timer时,我们同时也创建了一个TimerThread即thread,并且启动了这个线程,

public Timer(String name) {
        thread.setName(name);
        thread.start();
}

 TimerThread中的mainLoop方法是核心,它会完成所有的任务执行,在一开始我们的队列为空,这时mainLoop方法将会使线程进入等待状态,当我们使用schedule提交任务时会notify这个TimerThread线程,若任务的执行未到则在wait相对的时间差。

我们调整一下上面的代码,

		Timer timer = new Timer();
		timer.schedule(new TimerTask1(), 1);
		timer.schedule(new TimerTask1(), 5000);
		timer.schedule(new TimerLongTask(), 3000);
		TimeUnit.SECONDS.sleep(20);
		timer.cancel();

 这样先提交两个输出字符的任务最后提交长任务,在这里,我们让第二个输出字符的任务延迟5秒执行,长任务延迟3秒执行,这样得到的结果如下:

Thu Apr 21 13:07:44 CST 2011,2sstwluvgc
TimerLongTask: 开始沉睡
TimerLongTask: 已经醒来
Thu Apr 21 13:07:57 CST 2011,sh4fnkqqc8

 虽然我们改变了提交顺序,但是还是按照延迟时间递增排序执行的,两个输出字符串的时间之间相差13秒,这也是长任务等待执行时间+长任务睡眠时间之和。

 

重复执行scheduleAtFixedRate方法提交任务,主要是调用rescheduleMin方法对已经调用的任务进行重新设置调度延迟,并调用fixDown方法对队列里的任务根据延迟时间重新排序。

Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask1(), 3000, 5000);

 3000,代表第一次执行时等待的时间,5000代表每次执行任务之间的时间间隔,运行结果:

Thu Apr 21 13:14:55 CST 2011,izf536esrg
Thu Apr 21 13:15:00 CST 2011,2khzm7e09v
Thu Apr 21 13:15:05 CST 2011,jc3dvt2m8q

 基本是每5秒运行一次。

 

由于Timer只使用一个线程运行所有的任务,那么当一个任务抛出运行时异常后会有什么样的情形呢?其他的任务是否可以继续?我们已经有了前面的知识可以先猜想一个结果:因为Timer只使用一个线程运行所有的任务,所以当一个线程抛出运行时异常时,这个线程就基本挂了,不会在执行后续的任何代码,因此我们可以断言,当一个任务抛出运行时异常时,后续任务都不可以执行。为了证明这个猜想,我们需要一个可以抛出异常的任务,如下:

public class TimerExceptionTask extends TimerTask {

	@Override
	public void run() {
		System.out.println("TimerExceptionTask: "+new Date());
		throw new RuntimeException();
	}

}

 这个任务抛出一个运行时异常。接着我们需要定义一下我们任务执行的顺序:先执行一个正常的任务,然后在执行一个抛出异常的任务,最后在执行一个正常的任务,如下:

		Timer timer = new Timer();
		timer.schedule(new TimerTask1(), 1000);
		timer.schedule(new TimerExceptionTask(), 3000);
		timer.schedule(new TimerTask1(), 5000);
		TimeUnit.SECONDS.sleep(6);
		timer.cancel();

 延迟1秒执行正常输出字符串的任务,延迟3秒执行抛出异常的任务,延迟5秒执行正常输出字符串的任务,看一下结果:

Thu Apr 21 13:40:23 CST 2011,lk7fjneyyu
TimerExceptionTask: Thu Apr 21 13:40:25 CST 2011
Exception in thread "Timer-0" java.lang.RuntimeException
	at org.victorzhzh.concurrency.TimerExceptionTask.run(TimerExceptionTask.java:11)
	at java.util.TimerThread.mainLoop(Timer.java:512)
	at java.util.TimerThread.run(Timer.java:462)

 并没有输出两个字符串,只执行了第一次的输出字符串任务,说明当抛出运行时异常时,其后续任务不可能被执行。

鉴于Timer的缺陷,所以对它的使用还是要谨慎的,还好并发包中为我们提供了相应的替代品:ScheduledThreadPoolExecutor。

分享到:
评论
3 楼 laj12347 2015-03-26  
   写的非常细心,学习了。有理有据
2 楼 wangdy0987 2014-04-04  
例子写得挺好的。
1 楼 promzaid 2011-12-12  
写得挺好,学习了

相关推荐

    linux-timer-example.zip_Linux timer

    总结来说,Linux Timer是系统中的重要组成部分,它为开发者提供了灵活的延时和周期性任务执行机制。理解并熟练使用Linux Timer对于编写高效、可靠的系统级代码至关重要。通过这个压缩包中的教程,你可以系统地学习...

    7- Timer.zip

    - 定期器(Periodic Timer):周期性地产生中断,用于执行周期性任务,如实时操作系统(RTOS)的时钟节拍。 - 倒计时计时器(Down Counter):从预设值开始倒数,达到零时触发中断。 - 自由运行计时器(Free ...

    裸机-timer-module.7z

    在STM32系列芯片中,内置了多个定时器模块,如TIM1、TIM2、TIM3等,它们可以用来产生周期性中断、脉冲宽度调制(PWM)、捕获等功能。在本项目中,我们关注的是如何通过单个定时器实现多时基,这意味着一个定时器可以...

    isa-timer.rar_Work It

    文件“hdminvd0.c”可能涉及到硬盘驱动程序的源代码,这与ISA定时器有关,因为定时器也用于硬盘的I/O操作,如磁盘读写时间的管理,确保数据传输的准确性和效率。硬盘驱动程序需要与系统时钟同步,以优化访问时间和...

    迅雷定时器---定时启动迅雷

    在VC++中,可以使用Windows API中的SetTimer函数创建一个定时器,该函数会周期性地触发一个消息,这个消息可以被应用程序捕获并执行相应的操作,例如启动迅雷。定时器的间隔时间可以通过参数设置,以毫秒为单位。 2...

    BRT-Timer0-Timer1-同时编程脉冲输出.zip_C/C++_

    3. **脉冲输出**:脉冲输出是指单片机通过特定的I/O口产生周期性的高低电平变化,常用于驱动其他设备,如电机、LED等。在STC12C5A系列单片机中,可以通过设置I/O口的方向寄存器和数据寄存器来控制脉冲的高电平和低...

    Android-一个Android的rxjava2timer

    在Android开发中,RxJava2是...总的来说,RxJava2的`Timer`操作符在Android开发中提供了强大的定时功能,结合各种调度器和操作符,可以灵活地处理各种异步任务。正确理解和使用`Timer`能显著提升代码的可读性和维护性。

    TIMER-COUNTER-LED-ATMega8535.zip_atmega8535

    总之,利用ATMega8535的定时器计数器功能,我们可以实现精确的定时任务,如LED的周期性闪烁。通过理解和编程这些定时器,开发者能够掌握更高级的嵌入式系统设计技巧,这对于开发涉及实时控制和交互的项目至关重要。

    TIMER_轮询_裸机定时器任务调度_

    硬件定时器可以周期性地产生中断,提醒CPU执行预定的任务。常见的定时器有RTC(实时计时器)、PWM(脉宽调制)定时器、GPT(通用定时器)等。 3. **轮询**:轮询是一种任务调度策略,即程序会定期检查各个任务是否...

    单片机源码学习参考-TIMER0控制LED二进制计数.zip

    TIMER0是单片机中常见的定时器资源之一,通常用于执行周期性的任务,如定时中断、脉冲发生或计数。它的工作原理是根据预设的计数值,在内部时钟的驱动下自动递增,当计数值达到预设值时,会产生一个中断请求,然后...

    Exemplo2 asm - Timer0_assembly_pic16f877a_

    在定时模式下,Timer0从预设值开始递减,当达到零时产生中断,可用于周期性任务,如控制LED闪烁的频率。 4. **I/O端口**:PIC16F877A的I/O端口用于与外部设备交互,例如读取按钮状态和控制LED。按钮通常连接到输入...

    steady_timer使用

    这样可以在每次回调结束后更新定时器的过期时间,从而实现周期性的任务执行。 示例代码如下: ```cpp void print(const boost::system::error_code& e, boost::asio::steady_timer* t, int* count) { std::cout !...

    Timer.X_timer_

    定时器是微控制器中的一个核心组件,用于执行周期性任务、实现延时、中断服务以及许多其他时间相关的功能。 **XC8编译器和MPLABX IDE** MPLABX是Microchip推出的一款免费的、跨平台的IDE,它支持多种微控制器系列...

    TIMER0与TIMER1控制条形LED.rar

    这使得我们可以用它来实现周期性的任务,如控制LED的闪烁频率。而定时器1通常有更大的计数范围,更适合需要更长周期的任务。 在实际应用中,控制条形LED通常涉及到以下步骤: 1. 初始化定时器:设置定时器的工作...

    SYD8801_TIMER

    - **周期定时模式**:定时器在达到预设值后产生中断请求,然后自动重置并重新开始计数,形成周期性的定时事件。 4. **振荡器**: 提到的“05Timer_32KXtal”可能是指SYD8801可以连接32kHz的晶体振荡器,为计数器...

    timer-shi-zhong.zip_msp430f149

    4. **中断系统**:MSP430F149的中断系统是其处理外部事件和实时性任务的关键。定时器A产生的中断请求会被CPU捕获,执行中断服务程序,从而更新数码管显示的时间。 5. **10秒间隔的设定**:要使定时器A每10秒中断一...

    TIMER0控制四只LED滚动闪烁.rar

    在单片机编程中,TIMER0通常是一种基本的定时/计数器资源,用于执行周期性任务或实现精确的时间延迟。这个项目中,TIMER0被配置为自动重载模式,以便在达到预设时间后自动重新加载计数值,从而持续产生定时中断。...

    nios-II.rar_Nios II_interrupt nios_nios_nios ii timer_nios timer

    - 定时器可以设置为周期性中断,当达到预设值时触发中断,常用于调度任务、延迟操作或硬件同步。 - 定时器的使用涉及配置寄存器、设定计数值以及编写中断服务例程。 4. **Nios II Timer中断**: - 当定时器计数...

    第五章MCS-51单片机中断定时及通信系统--通信系统.zip

    定时模式下,定时器按照预设的时间周期自动重装载,用于实现延时或者周期性任务;计数模式则对脉冲信号进行计数。此外,还有波特率发生器,通过定时器配合分频器,生成串行通信所需的波特率。 三、通信系统 MCS-51...

    NXP i.MX RT1052 RT-Thread实战:软件定时器

    软件定时器是RT-Thread中的一个重要组件,用于实现周期性任务或者在特定时间点执行的任务。 软件定时器在RT-Thread中分为两种类型:一次性定时器和周期性定时器。一次性定时器会在设定的时间间隔后触发一次回调函数...

Global site tag (gtag.js) - Google Analytics