xylz,imxylz

关注后端架构、中间件、分布式和并发编程

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  111 随笔 :: 10 文章 :: 2680 评论 :: 0 Trackbacks
[本文地址:http://www.blogjava.net/Files/xylz/Inside.Java.Concurrency_32.ThreadPool.part5_ScheduledExecutorService.pdf]

周期性任务调度前世

在JDK 5.0之前,java.util.Timer/TimerTask是唯一的内置任务调度方法,而且在很长一段时间里很热衷于使用这种方式进行周期性任务调度。

首先研究下Timer/TimerTask的特性(至于javax.swing.Timer就不再研究了)。

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

 

public class Timer {

   
private TaskQueue queue = new TaskQueue();
   
/**
     * The timer thread.
    
*/
   
private TimerThread thread = new TimerThread(queue);
 

 

java.util.TimerThread.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();
               
if (queue.isEmpty())
                   
break; // Queue is empty and will forever remain; die
。。。。。。
                
               
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/TimerTask的以下特性:

  • Timer对任务的调度是基于绝对时间的。
  • 所有的TimerTask只有一个线程TimerThread来执行,因此同一时刻只有一个TimerTask在执行。
  • 任何一个TimerTask的执行异常都会导致Timer终止所有任务。
  • 由于基于绝对时间并且是单线程执行,因此在多个任务调度时,长时间执行的任务被执行后有可能导致短时间任务快速在短时间内被执行多次或者干脆丢弃多个任务。

由于Timer/TimerTask有这些特点(缺陷),因此这就导致了需要一个更加完善的任务调度框架来解决这些问题。

周期性任务调度今生

java.util.concurrent.ScheduledExecutorService的出现正好弥补了Timer/TimerTask的缺陷。

public interface ScheduledExecutorService extends ExecutorService {
   
public ScheduledFuture<?> schedule(Runnable command,
                      
long delay, TimeUnit unit);
 
   
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                      
long delay, TimeUnit unit);
   
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                         
long initialDelay,
                         
long period,
                          TimeUnit unit);
 
   
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                            
long initialDelay,
                            
long delay,
                             TimeUnit unit);
}

 

首先ScheduledExecutorService基于ExecutorService,是一个完整的线程池调度。另外在提供线程池的基础上增加了四个调度任务的API。

  • schedule(Runnable command,long delay, TimeUnit unit):在指定的延迟时间一次性启动任务(Runnable),没有返回值。
  • schedule(Callable<V> callable, long delay, TimeUnit unit):在指定的延迟时间一次性启动任务(Callable),携带一个结果。
  • scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。如果任务的任何一个执行遇到异常,则后续执行都会被取消。否则,只能通过执行程序的取消或终止方法来终止该任务。如果此任务的任何一个执行要花费比其周期更长的时间,则将推迟后续执行,但不会同时执行。
  • scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit):创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。如果任务的任一执行遇到异常,就会取消后续执行。否则,只能通过执行程序的取消或终止方法来终止该任务。

上述API解决了以下几个问题:

  • ScheduledExecutorService任务调度是基于相对时间,不管是一次性任务还是周期性任务都是相对于任务加入线程池(任务队列)的时间偏移。
  • 基于线程池的ScheduledExecutorService允许多个线程同时执行任务,这在添加多种不同调度类型的任务是非常有用的。
  • 同样基于线程池的ScheduledExecutorService在其中一个任务发生异常时会退出执行线程,但同时会有新的线程补充进来进行执行。
  • ScheduledExecutorService可以做到不丢失任务。

下面的例子演示了一个任务周期性调度的例子。

package xylz.study.concurrency.executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorServiceDemo {
   
public static void main(String[] args) throws Exception{
        ScheduledExecutorService execService
=   Executors.newScheduledThreadPool(3);
        execService.scheduleAtFixedRate(
new Runnable() {
           
public void run() {
                System.out.println(Thread.currentThread().getName()
+" -> "+System.currentTimeMillis());
               
try {
                    Thread.sleep(
2000L);
                }
catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },
1, 1, TimeUnit.SECONDS);
       
//
        execService.scheduleWithFixedDelay(new Runnable() {
           
public void run() {
                System.out.println(Thread.currentThread().getName()
+" -> "+System.currentTimeMillis());
               
try {
                    Thread.sleep(
2000L);
                }
catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },
1, 1, TimeUnit.SECONDS);
        Thread.sleep(
5000L);
        execService.shutdown();
    }
}

 

一次可能的输出如下:

pool-1-thread-1 -> 1294672392657
pool
-1-thread-2 -> 1294672392659
pool
-1-thread-1 -> 1294672394657
pool
-1-thread-2 -> 1294672395659
pool
-1-thread-1 -> 1294672396657

 

在这个例子中启动了默认三个线程的线程池,调度两个周期性任务。第一个任务是每隔1秒执行一次,第二个任务是以固定1秒的间隔执行,这两个任务每次执行的时间都是2秒。上面的输出有以下几点结论:

  • 任务是在多线程中执行的,同一个任务应该是在同一个线程中执行。
  • scheduleAtFixedRate是每次相隔相同的时间执行任务,如果任务的执行时间比周期还长,那么下一个任务将立即执行。例如这里每次执行时间2秒,而周期时间只有1秒,那么每次任务开始执行的间隔时间就是2秒。
  • scheduleWithFixedDelay描述是下一个任务的开始时间与上一个任务的结束时间间隔相同。流入这里每次执行时间2秒,而周期时间是1秒,那么两个任务开始执行的间隔时间就是2+1=3秒。

事实上ScheduledExecutorService的实现类ScheduledThreadPoolExecutor是继承线程池类ThreadPoolExecutor的,因此它拥有线程池的全部特性。但是同时又是一种特殊的线程池,这个线程池的线程数大小不限,任务队列是基于DelayQueue的无限任务队列。具体的结构和算法在以后的章节中分析。

由于ScheduledExecutorService拥有Timer/TimerTask的全部特性,并且使用更简单,支持并发,而且更安全,因此没有理由继续使用Timer/TimerTask,完全可以全部替换。需要说明的一点事构造ScheduledExecutorService线程池的核心线程池大小要根据任务数来定,否则可能导致资源的浪费。

 

[本文地址:http://www.blogjava.net/Files/xylz/Inside.Java.Concurrency_32.ThreadPool.part5_ScheduledExecutorService.pdf]


©2009-2014 IMXYLZ |求贤若渴
posted on 2011-01-10 23:39 imxylz 阅读(14430) 评论(32)  编辑  收藏 所属分类: Java Concurrency

评论

# re: 深入浅出 Java Concurrency (32): 线程池 part 5 周期性任务调度 2011-01-11 12:06 打底裤
果然深入浅出哈  回复  更多评论
  

# re: 深入浅出 Java Concurrency (32): 线程池 part 5 周期性任务调度 2011-01-12 15:59 青菜猫
楼主确实不错,。有机会来杭州一起给我们团队讲下  回复  更多评论
  

# re: 深入浅出 Java Concurrency (32): 线程池 part 5 周期性任务调度 2011-02-09 16:28 wells
海,兄弟可以交流下  回复  更多评论
  

# re: 深入浅出 Java Concurrency (32): 线程池 part 5 周期性任务调度 2011-02-09 16:30 wells
我是淘宝技术人员,有几个问题想跟你交流下,我的邮箱是qing.yinbo@gmail.com,通过邮件我们可以交流下,期待你的email  回复  更多评论
  

# re: 深入浅出 Java Concurrency (32): 线程池 part 5 周期性任务调度 2013-11-08 12:34 高永飞
同样基于线程池的ScheduledExecutorService在其中一个任务发生异常时会退出执行线程,但同时会有新的线程补充进来进行执行

这句话有问题,任务发生RuntimeException时,执行该任务的线程会终止,这句话没有问题。但是不会有新的线程补充进来执行。因为执行任务的后台线程是调用scheduleWithFixedDelay或scheduleWithFixedDelay时创建的,可以参考ScheduledThreadPoolExecutor.delayedExecute的prestartCoreThread()。

也就是说,如果想让任务发生异常时照样按时执行后续的任务,需要在任务里catch住所有异常。

举个例子,
ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);
exec.scheduleWithFixedDelay(new Runnable() 。。。

ScheduledExecutorService不会因为设置了size是2,而会在任务发生异常时,再启动一个线程继续执行。

技术性的文章建议博主写代码检验每一句话,尤其是博主这种浏览量大的文章。
不过真心赞一下,博主写的这一系列文章很不错!

如果我说的有问题,请及时指正 qq:554312685  回复  更多评论
  

# John 2014-05-02 03:26 Smithd559
Good writeup, I am normal visitor of ones blog, maintain up the excellent operate, and It's going to be a regular visitor for a lengthy time. kbceedeffdadcddg  回复  更多评论
  

# Good info 2014-05-04 21:05 Pharmd383
Very nice site!  回复  更多评论
  

# cheap cialis online 2014-05-16 16:48 cialis
Hello!
  回复  更多评论
  

# cheap cialis 2014-05-16 16:49 cheap_cialis
Hello!
  回复  更多评论
  

# viagra sale 2014-05-17 05:07 viagra_sale
Hello!
  回复  更多评论
  

# cialis tadalafil cheapest online 2014-05-17 05:28 tadalafil
Hello!
  回复  更多评论
  

# cialis online 2014-05-20 15:19 cialis_online
Hello!
  回复  更多评论
  

# viagra price 2014-05-20 15:20 viagra_price
Hello!
  回复  更多评论
  

# cheap cialis 2014-07-14 18:44 cheap_cialis
Hello!
  回复  更多评论
  

# cheap viagra 2014-07-14 18:45 cheap_viagra
Hello!
  回复  更多评论
  

# http://canadianviagragen7a.com/ 2014-07-17 15:14 with
Hello!
  回复  更多评论
  

# http://viagra7withoutprescription.com/ 2014-07-17 18:09 discount
Hello!
  回复  更多评论
  

# http://cheapgeneric7viagra.com/ 2014-07-17 18:10 discount
Hello!
  回复  更多评论
  

# buy generic cialis 2014-07-17 23:53 generic
Hello!
  回复  更多评论
  

# cialis fast delivery 2015-04-29 19:50 fast
Hello!
  回复  更多评论
  

# viagra fast delivery 2015-04-29 19:50 fast
Hello!
  回复  更多评论
  

# John 2015-06-01 07:45 Smithc667
Thanksamundo for the post.Really thank you! Awesome. edebdbceaekadffd  回复  更多评论
  

# cialis side effects 2016-03-30 22:23 side
Hello!
  回复  更多评论
  

# cialis 2016-04-05 23:27 cialis
Hello!
  回复  更多评论
  

# dosage of viagra 2016-04-05 23:27 of
Hello!
  回复  更多评论
  

# cialis 2016-04-05 23:28 cialis
Hello!
  回复  更多评论
  

# viagra 2016-04-05 23:29 viagra
Hello!
  回复  更多评论
  

# cialis 2016-04-06 07:16 cialis
Hello!
  回复  更多评论
  

# dosage of viagra 2016-04-06 07:16 of
Hello!
  回复  更多评论
  

# order cialis 2016-04-06 07:17 order_cialis
Hello!
  回复  更多评论
  

# order viagra 2016-04-06 07:18 order_viagra
Hello!
  回复  更多评论
  

# re: 深入浅出 Java Concurrency (32): 线程池 part 5 周期性任务调度 2018-08-03 17:21 bboymars
任务是在多线程中执行的,同一个任务应该是在同一个线程中执行。
这个结论在我机子上运行不对,我运行如下:
pool-1-thread-1 -> 任务a -> 1533287852227
pool-1-thread-2 -> 任务b -> 1533287852227
pool-1-thread-2 -> 任务a -> 1533287854227
pool-1-thread-1 -> 任务b -> 1533287855230
pool-1-thread-2 -> 任务a -> 1533287856230
pool-1-thread-2 -> 任务a -> 1533287858234
pool-1-thread-1 -> 任务b -> 1533287858234
pool-1-thread-3 -> 任务a -> 1533287860236
pool-1-thread-2 -> 任务b -> 1533287861237
pool-1-thread-3 -> 任务a -> 1533287862237
pool-1-thread-2 -> 任务a -> 1533287864240
pool-1-thread-1 -> 任务b -> 1533287864240
pool-1-thread-2 -> 任务a -> 1533287866244  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航:
 

©2009-2014 IMXYLZ