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

今天一个同事问过我这样一个问题。

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。所以第二次调用必须等待

第一次执行完毕或才能够继续往下执行。






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值