分享

Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代

 WindySky 2017-10-15

继续并发,上篇博客对于ScheduledThreadPoolExecutor没有进行介绍,说过会和Timer一直单独写一篇Blog.

1、Timer管理延时任务的缺陷

a、以前在项目中也经常使用定时器,比如每隔一段时间清理项目中的一些垃圾文件,每个一段时间进行数据清洗;然而Timer是存在一些缺陷的,因为Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷:下面看例子:

Timer的源码:

  1. public class Timer {  
  2.     /** 
  3.      * The timer task queue.  This data structure is shared with the timer 
  4.      * thread.  The timer produces tasks, via its various schedule calls, 
  5.      * and the timer thread consumes, executing timer tasks as appropriate, 
  6.      * and removing them from the queue when they're obsolete. 
  7.      */  
  8.     private TaskQueue queue = new TaskQueue();  
  9.   
  10.     /** 
  11.      * The timer thread. 
  12.      */  
  13.     private TimerThread thread = new TimerThread(queue);  

TimerThread是Thread的子类,可以看出内部只有一个线程。下面看个例子:

  1. package com.zhy.concurrency.timer;  
  2.   
  3. import java.util.Timer;  
  4. import java.util.TimerTask;  
  5.   
  6. public class TimerTest  
  7. {  
  8.     private static long start;  
  9.   
  10.     public static void main(String[] args) throws Exception  
  11.     {  
  12.   
  13.         TimerTask task1 = new TimerTask()  
  14.         {  
  15.             @Override  
  16.             public void run()  
  17.             {  
  18.   
  19.                 System.out.println("task1 invoked ! "  
  20.                         + (System.currentTimeMillis() - start));  
  21.                 try  
  22.                 {  
  23.                     Thread.sleep(3000);  
  24.                 } catch (InterruptedException e)  
  25.                 {  
  26.                     e.printStackTrace();  
  27.                 }  
  28.   
  29.             }  
  30.         };  
  31.         TimerTask task2 = new TimerTask()  
  32.         {  
  33.             @Override  
  34.             public void run()  
  35.             {  
  36.                 System.out.println("task2 invoked ! "  
  37.                         + (System.currentTimeMillis() - start));  
  38.             }  
  39.         };  
  40.         Timer timer = new Timer();  
  41.         start = System.currentTimeMillis();  
  42.         timer.schedule(task1, 1000);  
  43.         timer.schedule(task2, 3000);  
  44.   
  45.     }  
  46. }  

定义了两个任务,预计是第一个任务1s后执行,第二个任务3s后执行,但是看运行结果:
  1. task1 invoked ! 1000  
  2. task2 invoked ! 4000  
task2实际上是4后才执行,正因为Timer内部是一个线程,而任务1所需的时间超过了两个任务间的间隔导致。下面使用ScheduledThreadPool解决这个问题:

  1. package com.zhy.concurrency.timer;  
  2.   
  3. import java.util.TimerTask;  
  4. import java.util.concurrent.Executors;  
  5. import java.util.concurrent.ScheduledExecutorService;  
  6. import java.util.concurrent.TimeUnit;  
  7.   
  8. public class ScheduledThreadPoolExecutorTest  
  9. {  
  10.     private static long start;  
  11.   
  12.     public static void main(String[] args)  
  13.     {  
  14.         /** 
  15.          * 使用工厂方法初始化一个ScheduledThreadPool 
  16.          */  
  17.         ScheduledExecutorService newScheduledThreadPool = Executors  
  18.                 .newScheduledThreadPool(2);  
  19.           
  20.         TimerTask task1 = new TimerTask()  
  21.         {  
  22.             @Override  
  23.             public void run()  
  24.             {  
  25.                 try  
  26.                 {  
  27.   
  28.                     System.out.println("task1 invoked ! "  
  29.                             + (System.currentTimeMillis() - start));  
  30.                     Thread.sleep(3000);  
  31.                 } catch (Exception e)  
  32.                 {  
  33.                     e.printStackTrace();  
  34.                 }  
  35.   
  36.             }  
  37.         };  
  38.   
  39.         TimerTask task2 = new TimerTask()  
  40.         {  
  41.             @Override  
  42.             public void run()  
  43.             {  
  44.                 System.out.println("task2 invoked ! "  
  45.                         + (System.currentTimeMillis() - start));  
  46.             }  
  47.         };  
  48.         start = System.currentTimeMillis();  
  49.         newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);  
  50.         newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);  
  51.     }  
  52. }  

输出结果:

  1. task1 invoked ! 1001  
  2. task2 invoked ! 3001  
符合我们的预期结果。因为ScheduledThreadPool内部是个线程池,所以可以支持多个任务并发执行。

2、Timer当任务抛出异常时的缺陷

如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行:

  1. package com.zhy.concurrency.timer;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Timer;  
  5. import java.util.TimerTask;  
  6.   
  7.   
  8. public class ScheduledThreadPoolDemo01  
  9. {  
  10.   
  11.   
  12.     public static void main(String[] args) throws InterruptedException  
  13.     {  
  14.   
  15.         final TimerTask task1 = new TimerTask()  
  16.         {  
  17.   
  18.             @Override  
  19.             public void run()  
  20.             {  
  21.                 throw new RuntimeException();  
  22.             }  
  23.         };  
  24.   
  25.         final TimerTask task2 = new TimerTask()  
  26.         {  
  27.   
  28.             @Override  
  29.             public void run()  
  30.             {  
  31.                 System.out.println("task2 invoked!");  
  32.             }  
  33.         };  
  34.           
  35.         Timer timer = new Timer();  
  36.         timer.schedule(task1, 100);  
  37.         timer.scheduleAtFixedRate(task2, new Date(), 1000);  
  38.           
  39.           
  40.   
  41.     }  
  42. }  

上面有两个任务,任务1抛出一个运行时的异常,任务2周期性的执行某个操作,输出结果:

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

由于任务1的一次,任务2也停止运行了。。。下面使用ScheduledExecutorService解决这个问题:

  1. package com.zhy.concurrency.timer;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Timer;  
  5. import java.util.TimerTask;  
  6. import java.util.concurrent.Executors;  
  7. import java.util.concurrent.ScheduledExecutorService;  
  8. import java.util.concurrent.TimeUnit;  
  9.   
  10.   
  11. public class ScheduledThreadPoolDemo01  
  12. {  
  13.   
  14.   
  15.     public static void main(String[] args) throws InterruptedException  
  16.     {  
  17.   
  18.         final TimerTask task1 = new TimerTask()  
  19.         {  
  20.   
  21.             @Override  
  22.             public void run()  
  23.             {  
  24.                 throw new RuntimeException();  
  25.             }  
  26.         };  
  27.   
  28.         final TimerTask task2 = new TimerTask()  
  29.         {  
  30.   
  31.             @Override  
  32.             public void run()  
  33.             {  
  34.                 System.out.println("task2 invoked!");  
  35.             }  
  36.         };  
  37.           
  38.           
  39.           
  40.         ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);  
  41.         pool.schedule(task1, 100, TimeUnit.MILLISECONDS);  
  42.         pool.scheduleAtFixedRate(task2, 0 , 1000, TimeUnit.MILLISECONDS);  
  43.   
  44.     }  
  45. }  

代码基本一致,但是ScheduledExecutorService可以保证,task1出现异常时,不影响task2的运行:

  1. task2 invoked!  
  2. task2 invoked!  
  3. task2 invoked!  
  4. task2 invoked!  
  5. task2 invoked!<span style="font-family: Arial, Helvetica, sans-serif;">...</span>  

3、Timer执行周期任务时依赖系统时间

Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化会出现一些执行上的变化,ScheduledExecutorService基于时间的延迟,不会由于系统时间的改变发生执行变化。


上述,基本说明了在以后的开发中尽可能使用ScheduledExecutorService(JDK1.5以后)替代Timer。


好了,如果博客中存在错误,请留言指出~


 



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多