pool(三)——Timer

本文解析了Java Timer的结构,包括Timer、TaskQueue和TimerThread的关系,重点讲解了任务调度、优先级排序、线程执行逻辑,以及如何确保任务按期执行。通过实例测试展示了任务执行顺序和超时处理机制。
摘要由CSDN通过智能技术生成

 1.关于Timer的三个维度

首先是 {@link java.util.Timer},这个是最外层的类,其中包含了{@link java.util.TaskQueue},这个是存放{@link java.util.TimerTask}的队列——a priority queue of TimerTasks。

第二层是 {@link java.util.TimerThread},这个是{@link java.util.Timer}在初始化的时候创建并启动的一个线程,这个线程取任务并且执行。

 /**
     * Creates a new timer whose associated thread has the specified name.
     * The associated thread does <i>not</i>
     * {@linkplain Thread#setDaemon run as a daemon}.
     *
     * @param name the name of the associated thread
     * @throws NullPointerException if {@code name} is null
     * @since 1.5
     */
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

2.TimerThread

Thread的子类,在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
            }
        }
    }
/**
     * 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) {
            }
        }
    }

3.task的创建——schedule方法

public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }
/**
     * Schedule the specified timer task for execution at the specified
     * time with the specified period, in milliseconds.  If period is
     * positive, the task is scheduled for repeated execution; if period is
     * zero, the task is scheduled for one-time execution. Time is specified
     * in Date.getTime() format.  This method checks timer state, task state,
     * and initial execution time, but not period.
     *
     * @throws IllegalArgumentException if <tt>time</tt> is negative.
     * @throws IllegalStateException if task was already scheduled or
     *         cancelled, timer was cancelled, or timer thread terminated.
     * @throws NullPointerException if {@code task} is null
     */
    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();
        }
    }

1.两重锁,先锁队列queue,再锁task,task中有一个Object对象作为锁
2.设置TimerTask的下次执行时间
{@link java.util.TimerTask#nextExecutionTime} = System.currentTimeMillis()+delay
3.将任务添加到队列中
{@link java.util.Timer#queue}
4.当前任务如果是队列的一个任务,就执行
task == {@link java.util.TaskQueue#getMin},调用queue的notify方法。如果不是,说明前面还有等待执行的task,只入队列,不用调用notify方法。
5.{@link java.util.TimerThread#mainLoop}
{@link java.util.TimerThread}会在{@link java.util.Timer}
构造的时候启动,进而调用mainLoop方法。
最开始queue是空的,所以queue.await(),当前线程挂起,当Timer中添加TimerTask任务时,
就会调用queue.notify()方法唤醒mainLoop线程。
 

4.根据优先级对堆进行重排序

1.TimerTask的入堆(queue)操作

delay时间设置的很长,就是为了让任务不执行,看看入队列的比较操作,period这里完全用作标志位,在debug的时候作为标记区分不同的Task,看看排序状况。
这里以3个为例,第一个入队列,index是1,第二个入队列,index是2,2>>1=1,然后拿queue[2]和queue[1]比较下次执行时间,queue[2]比queue[1]早,所以交换顺序。
第三个入队列,index是3,3>>1=1,和第一个比,queue[3]比queue[1]要早,所以交换顺序,所以现在queue[1]最早执行,queue[2]和queue[3]的顺序没有考虑。每次入队列的重排序操作在 {@link java.util.TaskQueue#fixUp} 方法中进行

/**
     * Establishes the heap invariant (described above) assuming the heap
     * satisfies the invariant except possibly for the leaf-node indexed by k
     * (which may have a nextExecutionTime less than its parent's).
     *
     * This method functions by "promoting" queue[k] up the hierarchy
     * (by swapping it with its parent) repeatedly until queue[k]'s
     * nextExecutionTime is greater than or equal to that of its parent.
     */
    private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

2.mainLoop执行操作:

每次取queue的第一个task,如果该task还没到执行时间,就等待对应的时间queue.wait(executionTime - currentTime)。

if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);

如果这期间又来了一个优先级更高(执行顺序更靠前)的task,入队列时调用fixUp把当前task排到队列头(优先级更高),然后notify这个queue打断这个wait,重新去取优先级更高的task。

/**
     * Adds a new task to the priority queue.
     */
    void add(TimerTask task) {
        // Grow backing store if necessary
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);

        queue[++size] = task;
        fixUp(size);
    }

如果到了执行时间(wait结束),在下次循环的时候,就执行该task。

如果是非重复任务,调用removeMin移除当前任务,在removeMin中fixDown,进行堆重排序。

如果是重复任务的话,还要调用rescheduleMin设置下次执行的时间,在rescheduleMin中调用fixDown,进行堆重排序。

3.{@link java.util.TaskQueue#fixDown}

/**
     * Establishes the heap invariant (described above) in the subtree
     * rooted at k, which is assumed to satisfy the heap invariant except
     * possibly for node k itself (which may have a nextExecutionTime greater
     * than its children's).
     *
     * This method functions by "demoting" queue[k] down the hierarchy
     * (by swapping it with its smaller child) repeatedly until queue[k]'s
     * nextExecutionTime is less than or equal to those of its children.
     */
    private void fixDown(int k) {
        int j;
        while ((j = k << 1) <= size && j > 0) {
            if (j < size &&
                queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                j++; // j indexes smallest kid
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

对剩下的Task进行重新排序,把下次执行时间最小的转移到第一个任务的位置。

测试用例:

public static void test1() {
        timer.schedule(new TimerTask() {
            @Override public void run() {
                System.out.println("Time's up 1!---" + new Date().toString());
                //                    SleepUtil.sleep(30000);
            }
        }, 2000 * 1000, 11 * 1000);
        timer.schedule(new TimerTask() {
            @Override public void run() {
                System.out.println("Time's up 2!---" + new Date().toString());
                //                    SleepUtil.sleep(30000);
            }
        }, 1500 * 1000, 22 * 1000);
        timer.schedule(new TimerTask() {
            @Override public void run() {
                System.out.println("Time's up 3!---" + new Date().toString());
                //                    SleepUtil.sleep(30000);
            }
        }, 5 * 1000, 333333 * 1000);
    }

5.TimerTask的执行顺序

可以得知Timer内部是单线程执行task的,一个timer对象只会启用一个TimerThread的。

当一个timer执行多个任务时,如果一个任务执行的时间过长,后面任务执行的时间可能就不是你预期执行的时间了,因为一个任务执行完了才会执行下个任务。

测试用例:

public static void test3() {
        timer.schedule(new TimerTask() {
            @Override public void run() {
                System.out.println("Time's up 1!---" + new Date().toString());
                SleepUtil.sleep(0);
            }
        }, 0, 2 * 1000);
        timer.schedule(new TimerTask() {
            @Override public void run() {
                System.out.println("Time's up 2!---" + new Date().toString());
                SleepUtil.sleep(5000);
            }
        }, 0, 2 * 1000);
        // EvictionTimer.schedule(evictor, delay, delay);
    }

TimerTask执行时间过长,超过了period,执行5s,period是2s,这样period相当于失效了。因为下次执行的时间是这样计算的,
{@link java.util.TimerTask#nextExecutionTime} = System.currentTimeMillis()+delay
所以,当本次任务执行结束(过了超过5s),到下次任务取出来判断的执行时间的时候,肯定已经超过了原本应该执行的时间。

根据mainLoop中的逻辑,当判断执行时间比当前时间要早的话,直接执行本次任务。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值