在java.lang.Timer为非守护线程的情况下,加入到TimerTask的任务执行完毕了,Timer线程仍在继续运行的原因

问题引入:  假设当前时间为2019-03-13 22:10:00 ,定义任务A在3分钟后执行,任务B在5分钟后执行,我们将A和B加入至Timer中,根据Timer中TaskQueue顺序启动任务的原则,8分钟后任务A和任务B都会执行完毕。问题来了,明明任务全部执行完毕了,为什么Timer线程仍在继续 执行,就仿佛停不下来了呢?

 

首先,我们来看看Timer的构造函数

public Timer() {
   this("Timer-" + serialNumber());
}

接着,点开this ()发现实际调用的构造函数如下

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

 

/**
 * The timer thread.
*/
private final TimerThread thread = new TimerThread(queue);

说白了就是Timer自己启动了一个私有线程(外部无法调用)去执行相关操作。因此,需要看看TimeThread的run()到底是怎么写的

public void run() {
	try {
		mainLoop();
	} finally {
		// Someone killed this Thread, behave as if Timer cancelled
		synchronized(queue) {
			newTasksMayBeScheduled = false;
			queue.clear();  // Eliminate obsolete references
		}
	}
}

再看mainLoop()是如何实现的

/**
 * The main timer loop.  (See class comment.)
 */
private void mainLoop() {
	while (true) {
		try {
			TimerTask task;
			boolean taskFired;
			synchronized(queue) {
				// Wait for queue to become non-empty
				while (queue.isEmpty() && newTasksMayBeScheduled)
					queue.wait();
				if (queue.isEmpty())
					break; // Queue is empty and will forever remain; die

				// Queue nonempty; look at first evt and do the right thing
				long currentTime, executionTime;
				task = queue.getMin();
				synchronized(task.lock) {
					if (task.state == TimerTask.CANCELLED) {
						queue.removeMin();
						continue;  // No action required, poll queue again
					}
					currentTime = System.currentTimeMillis();
					executionTime = task.nextExecutionTime;
					if (taskFired = (executionTime<=currentTime)) {
						if (task.period == 0) { // Non-repeating, remove
							queue.removeMin();
							task.state = TimerTask.EXECUTED;
						} else { // Repeating task, reschedule
							queue.rescheduleMin(
							  task.period<0 ? currentTime   - task.period
											: executionTime + task.period);
						}
					}
				}
				if (!taskFired) // Task hasn't yet fired; wait
					queue.wait(executionTime - currentTime);
			}
			if (taskFired)  // Task fired; run it, holding no locks
				task.run();
		} catch(InterruptedException e) {
		}
	}
}

这样就很好理解了。

线程拿到queue对象锁,进入同步代码块后首当其冲会遇到while(queue.isEmpty() && newTasksMayBeScheduled), 由于刚刚初始化Timer,queue自然是空的,并且newTasksMayBeScheduled的初始值也为true,所以执行了queue.wait()  因此当前持有锁的线程——Timer自然而然的进入了等待状态。

既然有wait(),自然就少不了notify()。让我们看看Timer.java中的哪个部分调用了notify()

//外部类 将task1加入至Timer中
timer.schedule(task1, Date date);

public void schedule(TimerTask task, Date time) {
    sched(task, time.getTime(), 0);
}

private void sched(TimerTask task, long time, long period) {
	if (time < 0)
		throw new IllegalArgumentException("Illegal execution time.");

	// Constrain value of period sufficiently to prevent numeric
	// overflow while still being effectively infinitely large.
	if (Math.abs(period) > (Long.MAX_VALUE >> 1))
		period >>= 1;

	synchronized(queue) {
		if (!thread.newTasksMayBeScheduled)
			throw new IllegalStateException("Timer already cancelled.");

		synchronized(task.lock) {
			if (task.state != TimerTask.VIRGIN)
				throw new IllegalStateException(
					"Task already scheduled or cancelled");
			task.nextExecutionTime = time;
			task.period = period;
			task.state = TimerTask.SCHEDULED;
		}

		queue.add(task);
		if (queue.getMin() == task)
			queue.notify();
	}
}

也即,当任务通过timer.schedule()加入至Timer时,会向队列中加入任务且唤醒Timer阻塞线程。

 

回过头来再看mainLoop()方法。本题的关键就在于while(queue.isEmpty()&newTasksMayBeScheduled),下方的注释已经写得非常明白了。

private void mainLoop() {
	while (true) {
		try {
			TimerTask task;
			boolean taskFired;
			synchronized(queue) {
				// 等待新的任务加入队列 
				// 注意: 如果所有的任务已经执行完毕,由于下方代码会主动调用run(),因此同样会再次执行此处代码
				// 此时,任务队列中已经没有任务了,Timer线程又一次回到了漫长的等待中,等待新的任务降临
				while (queue.isEmpty() && newTasksMayBeScheduled) 
					queue.wait();
				if (queue.isEmpty())
					break; // 如果队列为空,即没有任务可执行任务,且将来也不会有任务(没有等待的必要了),因此断掉循环,最终结束run(),线程终止

				// Queue nonempty; look at first evt and do the right thing
				long currentTime, executionTime;
				task = queue.getMin(); //若队列不为空,则获取队列头部的任务并加以执行(众所周知,队列的FIFO,头部存储的是最早被加入的任务)
				synchronized(task.lock) { //队列的对象锁  注意这里的lock不是ReentrantLock之流,不过是一个普通的Object对象而已。
					if (task.state == TimerTask.CANCELLED) {
						queue.removeMin();
						continue;  // No action required, poll queue again
					}
					currentTime = System.currentTimeMillis(); // 当前时间
					executionTime = task.nextExecutionTime; // 任务计划执行时间
					
					// 若任务计划执行时间早于或等于当前时间,那么就需要执行任务了,所以进入if()内部执行任务,既然任务开始执行了,所以taskFired被赋值true,表示任务已过期。
					// 若任务计划执行时间晚于当前时间,说明还没有轮到该任务执行呢,因此不执行if(),taskFired被赋值为false,表示任务尚未过期。
					if (taskFired = (executionTime<=currentTime)) { 
						if (task.period == 0) { // 0表示任务不重复执行
							queue.removeMin(); //移除头部任务
							task.state = TimerTask.EXECUTED; //将当前任务的状态标记为"已执行"  注意: 这里利用了异步执行的思想,任务本身由于继承了Runnable接口,被作为一个线程异步执行,与此同时Timer线程也不耽搁,为该任务的描述性属性赋值。
						} else { // 重复执行任务
							queue.rescheduleMin(
							  task.period<0 ? currentTime   - task.period
											: executionTime + task.period);
						}
					}
				}
				if (!taskFired) // 任务尚未开始,还需要等待,等待的时长 = 任务即将执行的时间 - 当前时间
					queue.wait(executionTime - currentTime);
			}
			if (taskFired)  // 任务已过期(执行过了),再次调用自身线程的run()
				task.run();
		} catch(InterruptedException e) {
		}
	}
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值