Timer的缺陷 用ScheduledExecutorService替代

原文地址
繼續併發,上篇博客對於ScheduledThreadPoolExecutor沒有進行介紹,說過會和Timer一直單獨寫一篇Blog.
1、Timer管理延時任務的缺陷
a、以前在項目中也經常使用定時器,比如每隔一段時間清理項目中的一些垃圾文件,每個一段時間進行數據清洗;然而Timer是存在一些缺陷的,因爲Timer在執行定時任務時只會創建一個線程,所以如果存在多個任務,且任務時間過長,超過了兩個任務的間隔時間,會發生一些缺陷:下面看例子:
Timer的源碼:

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

TimerThread是Thread的子類,可以看出內部只有一個線程。下面看個例子:

package com.zhy.concurrency.timer;

import java.util.Timer;
import java.util.TimerTask;

public class TimerTest
{
    private static long start;

    public static void main(String[] args) throws Exception
    {

        TimerTask task1 = new TimerTask()
        {
            @Override
            public void run()
            {

                System.out.println("task1 invoked ! "
                        + (System.currentTimeMillis() - start));
                try
                {
                    Thread.sleep(3000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }

            }
        };
        TimerTask task2 = new TimerTask()
        {
            @Override
            public void run()
            {
                System.out.println("task2 invoked ! "
                        + (System.currentTimeMillis() - start));
            }
        };
        Timer timer = new Timer();
        start = System.currentTimeMillis();
        timer.schedule(task1, 1000);
        timer.schedule(task2, 3000);

    }
}

定義了兩個任務,預計是第一個任務1s後執行,第二個任務3s後執行,但是看運行結果

task1 invoked ! 1000
task2 invoked ! 4000

task2實際上是4後才執行,正因爲Timer內部是一個線程,而任務1所需的時間超過了兩個任務間的間隔導致。下面使用ScheduledThreadPool解決這個問題:

package com.zhy.concurrency.timer;

import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExecutorTest
{
    private static long start;

    public static void main(String[] args)
    {
        /**
         * 使用工廠方法初始化一個ScheduledThreadPool
         */
        ScheduledExecutorService newScheduledThreadPool = Executors
                .newScheduledThreadPool(2);

        TimerTask task1 = new TimerTask()
        {
            @Override
            public void run()
            {
                try
                {

                    System.out.println("task1 invoked ! "
                            + (System.currentTimeMillis() - start));
                    Thread.sleep(3000);
                } catch (Exception e)
                {
                    e.printStackTrace();
                }

            }
        };

        TimerTask task2 = new TimerTask()
        {
            @Override
            public void run()
            {
                System.out.println("task2 invoked ! "
                        + (System.currentTimeMillis() - start));
            }
        };
        start = System.currentTimeMillis();
        newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);
        newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);
    }
}

輸出結果:

task1 invoked ! 1001
task2 invoked ! 3001

符合我們的預期結果。因爲ScheduledThreadPool內部是個線程池,所以可以支持多個任務併發執行。
2、Timer當任務拋出異常時的缺陷
如果TimerTask拋出RuntimeException,Timer會停止所有任務的運行:

package com.zhy.concurrency.timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;


public class ScheduledThreadPoolDemo01
{


    public static void main(String[] args) throws InterruptedException
    {

        final TimerTask task1 = new TimerTask()
        {

            @Override
            public void run()
            {
                throw new RuntimeException();
            }
        };

        final TimerTask task2 = new TimerTask()
        {

            @Override
            public void run()
            {
                System.out.println("task2 invoked!");
            }
        };

        Timer timer = new Timer();
        timer.schedule(task1, 100);
        timer.scheduleAtFixedRate(task2, new Date(), 1000);



    }
}

上面有兩個任務,任務1拋出一個運行時的異常,任務2週期性的執行某個操作,輸出結果:

task2 invoked!
Exception in thread "Timer-0" java.lang.RuntimeException
    at com.zhy.concurrency.timer.ScheduledThreadPoolDemo01$1.run(ScheduledThreadPoolDemo01.java:24)
    at java.util.TimerThread.mainLoop(Timer.java:512)
    at java.util.TimerThread.run(Timer.java:462)

由於任務1的一次,任務2也停止運行了。。。下面使用ScheduledExecutorService解決這個問題:

package com.zhy.concurrency.timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public class ScheduledThreadPoolDemo01
{


    public static void main(String[] args) throws InterruptedException
    {

        final TimerTask task1 = new TimerTask()
        {

            @Override
            public void run()
            {
                throw new RuntimeException();
            }
        };

        final TimerTask task2 = new TimerTask()
        {

            @Override
            public void run()
            {
                System.out.println("task2 invoked!");
            }
        };



        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.schedule(task1, 100, TimeUnit.MILLISECONDS);
        pool.scheduleAtFixedRate(task2, 0 , 1000, TimeUnit.MILLISECONDS);

    }
}

代碼基本一致,但是ScheduledExecutorService可以保證,task1出現異常時,不影響task2的運行:

task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!

3、Timer執行週期任務時依賴系統時間
Timer執行週期任務時依賴系統時間,如果當前系統時間發生變化會出現一些執行上的變化,ScheduledExecutorService基於時間的延遲,不會由於系統時間的改變發生執行變化。

上述,基本說明了在以後的開發中儘可能使用ScheduledExecutorService(JDK1.5以後)替代Timer。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章