多線程併發及併發(一)

Timer線程介紹

Timer 是一個用來將將要執行的任務放在後臺線程執行的工具。被執行性的任務可以執行一次就結束了,也可以通過設定的時間間隔來週期性的執行。

Timer的基本使用

創建一個 Timer 對象,調用其 schedule() 方法傳遞一個TimerTask ,同時設定多長時間後開始執行及執行週期。這樣,就可以執行任務了。代碼如下:

   public void test() {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                while (true) {
                    System.err.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, 10000);
    }

Timer 的源碼分析

Timer使用涉及的三個相關類

  • TimerThread 真正執行任務的線程,Thread 的子類
  • TimerTask 被執行的任務,可以看做一個Runnable 對象
  • TaskQueue 存放 TimerTask 的類,封裝了一個 TimerTask 數組用來保存待執行的任務

創建Timer對象

Timer timer = new Timer();

分析 Timer 的源碼,可以看到,Timer 對象分裝了 TaskQueue對象和 TimerThread對象

public class Timer {
	private final TaskQueue queue = new TaskQueue();
	
	 private final TimerThread thread = new TimerThread(queue);
	//省略其他代碼
}

在通過構造方法創建對象時,最中會調用如下構造方法,可以看出,對象創建過程中就開啓了線程。線程的 start() 方法最終會調用其 run() 方法。

  public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
  }

由於 TimerThread 繼承了 Thread ,同時重寫了父類的 run() 方法,所以最終會執行 TimerThreadrun() 方法。

class TimerThread{
	 private TaskQueue queue;

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

run() 方法會調用 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) {
            }
        }
    }

我們可以看到,mainLoop() 方法裏面有個死循環,同時對queue對象進行同步加鎖 synchronized ,先判斷任務列表 queue 是否爲空,如果爲空,則調用 queue.wait() 使當線程進行休眠狀態。所以說,如果不向 queue 中添加任務,那麼該線程將永遠處於休眠狀態。那麼如何添加任務呢? timer.schedule() 方法的作用就出來了。

添加任務

timer.schedule(TimerTask,long) ,最終會調用 sched() 方法。
添加任務的方法

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

添加任務過程中先進行各種條件的判斷,同樣也對 queue 對象進行了加鎖,調用 queue.add(task) 將任務添加到任務列表中。那麼任務添加了,如何執行呢? queue.notify() 會喚醒其他享有該對象的鎖的的線程繼續執行任務。我們可以看到另一個對該對象加鎖的線程正是TimerThread ,而此時 TimerThread 又調用了 wait() 方法處於等待狀態,此時 notify() 方法會通知它繼續執行任務。

執行任務

       再次回到TimerThreadmainLoop() 方法中,當線程被喚醒後,繼續執行字後的代碼。同時進行條件判斷,如果被喚醒後線程列表還是爲空,那麼線程到此結束。

       從線程列表中取出線程,查看其執行時間及是否需要週期性的執行。進行一系列操作後,開始執行任務,調用 task.run() 方法,這樣,我們創建的 TimerTask 對象的 run() 方法就會執行了。

       當我們不需要執行任務後,需要調用 timercancel() 方法取消任務執行。

  public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }

使用注意

  1. 通常我們在Android中會週期性的執行一些任務,當這些任務有和界面進行交互的功能時,我們退出界面前一定要先取消任務,不然會引發異常,導致程序掛掉。
  2. 當項目中存在大量的定期任務時,可以考慮使用Quartz
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章