您的位置:首页 > 其它

Timer 和TimerTask 定时任务是否多线程

2013-05-16 18:01 405 查看
今天一个同事问过我这样一个问题。

Timer 启动一个任务,每个1秒钟执行一次 。如果第一次执行这个任务Task需要5秒。

那么第二次执行这个任务是在第2秒开始,还是在5秒开始。

问题就此开始了。

(1)如果是在第2秒执行这个任务。那么Task中的数据在同一个时间有两个线程在操作。这种操作

造成显现就会出现数据混乱。线程不安全。两个线程同时拿到一个全局变量index=1,先后加1.结果变成3.

第一个线程打印2,第二个线程打印3。这个现象就会造成线程不安全。(jvm现实应该没有这么傻。)

(2)所有可能性是第二次执行这个任务是从第5秒开始。它会等第一次执行完后在开始执行。

测试代码:

public class Task extends TimerTask {
int index = 0;
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println(++index+"Thread "+this.scheduledExecutionTime());
Thread.sleep(999);//休息1秒
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
Timer timer = new Timer();
Task task = new Task();
timer.schedule(task, 1000,1000);
}
}
打印结果:

1ThreadName1368694219239
2ThreadName1368694219239
3ThreadName1368694219239
4ThreadName1368694219239
5ThreadName1368694219239
6ThreadName1368694224235
7ThreadName1368694224235
8ThreadName1368694224235
9ThreadName1368694224235
10ThreadName1368694224235


这还不能充分证明我的猜想。

下面来看一下Timer和TimerTask的部分源码。

public class Timer {
/**
* The timer task queue.  This data structure is shared with the timer
* thread.  The timer produces tasks, via its various schedule calls,
* and the timer thread consumes, executing timer tasks as appropriate,
* and removing them from the queue when they're obsolete.
*/
private TaskQueue queue = new TaskQueue();

/**
* The timer thread.
*/
private TimerThread thread = new TimerThread(queue);
在代码里我们经常创建一个Timer对象。Timer timer=new Timer();

这个时候会创建TimerThread(queue)对象。

TaskQueue队列是用来存放任务Task,可以多个,先进先出。只有等第一个执行完才能执行第二个依次执行。

TimerThread是用来控制Task任务执行。继续Thread类。先看一下TimerThread类。

public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear();  // Eliminate obsolete references
}
}
}
这里继承Thread类并实习run方法。这个主要关注在run方法中 mainLoop();

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();//queue对象元素个数为空。等待被唤醒。
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) {
}
}
}
}

来分析一下这代码,请注意我标红的代码。在创建Timer对象的时候,同时会初始化TimerThread ,并执行mainLoop这个方法。

这个时候queue对象被创建,但是queue对象没有元素。所以会执行queue.wait()。代码停止继续往下执行。queue等待被唤醒。

在执行Timer.schedule()方法中会调用sche()方法。此时会向queue中添加Task任务。 queue.add(task);并且唤醒queue对象queue.notify()。

回到mainLoop()方法,queue.notify()唤醒后将继续往下执行while中的代码,代码判断task任务状态是否被取消Task.cancelled。(可以通过Task.cancelled()方法取消)。

如果没有取消,在判断执行的时间。executionTime<currentTime 就执行task.run 启动任务。(注意task继承Runnable ,但是task.run并不是开启一个线程任务。只是普通

对象方法调用。这个线程是TimerThread开启的。)

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

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();
}
}


如果executionTime>currentTime,那么queue.wait(executionTime-currentTime)。
说了这么半天,不知道大家明白了没有Timer.scheduel(task,1000,1000)在第二次调用任务是从第5秒开始。

那是因为Time.scheduel启动定时任务,并不是多线程。而是单线程在while循环调用task.run。所以第二次调用必须等待

第一次执行完毕或才能够继续往下执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: