文档章节

Java并发编程14-ScheduledThreadPoolExecutor详解

王胜_淡如止水
 王胜_淡如止水
发布于 2017/02/28 23:08
字数 1224
阅读 73
收藏 1

    ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似,但ScheduledThreadPoolExecutor功能更强大、更灵活。Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。

1 运行机制

    ScheduledThreadPoolExecutor的执行示意图(本文基于JDK 6)如下图所示。

    ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改。

    (1)使用DelayQueue作为任务队列。

    (2)获取任务的方式不同(后文会说明)。

    (3)执行周期任务后,增加了额外的处理(后文会说明)。

2 实现原理

2.1 执行周期任务的过程

    前面我们提到过,ScheduledThreadPoolExecutor会把待调度的任务(ScheduledFutureTask)放到一个DelayQueue中。

    ScheduledFutureTask主要包含3个成员变量,如下。

    (1)long型成员变量time,表示这个任务将要被执行的具体时间。

    (2)long型成员变量sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。

    (3)long型成员变量period,表示任务执行的间隔周期。

    DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。

    首先,让我们看看ScheduledThreadPoolExecutor中的线程执行周期任务的过程。下图是ScheduledThreadPoolExecutor中的线程1执行某个周期任务的4个步骤。

    下面是对这4个步骤的说明。

    (1)线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前时间。

    (2)线程1执行这个ScheduledFutureTask。

    (3)线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间。

    (4)线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

2.2 take()方法的源码实现

     接下来,让我们看看上面的步骤(1)获取任务的过程。下面是DelayQueue.take()方法的源代码实现。

    public E take() throws InterruptedException {
		final ReentrantLock lock = this.lock;
		lock.lockInterruptibly();// 1
		try {
			for (;;) {
				E first = q.peek();
				if (first == null) {
					available.await();// 2.1
				} else {
					long delay = first.getDelay(TimeUnit.NANOSECONDS);
					if (delay > 0) {
						long tl = available.awaitNanos(delay);// 2.2
					} else {
						E x = q.poll();// 2.3.1
						assert x != null;
						if (q.size() != 0)
							available.signalAll();// 2.3.2
						return x;
					}
				}
			}
		} finally {
			lock.unlock();// 3
		}
	}

下图是DelayQueue.take()的执行示意图。

    如图所示,获取任务分为3大步骤。

    (1)获取Lock。

    (2)获取周期任务。

            如果PriorityQueue为空,当前线程到Condition中等待;否则执行下面的2.2。

            如果PriorityQueue的头元素的time时间比当前时间大,到Condition中等待到time时间;否则执行下面的2.3。

            获取PriorityQueue的头元素(2.3.1);如果PriorityQueue不为空,则唤醒在Condition中等待的所有线程(2.3.2)。

    (3)释放Lock。

    ScheduledThreadPoolExecutor在一个循环中执行步骤2,直到线程从PriorityQueue获取到一个元素之后(执行2.3.1之后),才会退出无限循环(结束步骤2)。

2.3 add()方法的源码实现

    最后,让我们看看ScheduledThreadPoolExecutor中的线程执行任务的步骤4,把ScheduledFutureTask放入DelayQueue中的过程。下面是DelayQueue.add()的源代码实现。

    public boolean offer(E e) {
		final ReentrantLock lock = this.lock;
		lock.lock();// 1
		try {
			E first = q.peek();
			q.offer(e);// 2.1
			if (first == null || e.compareTo(first) < 0)
				available.signalAll();// 2.2
			return true;
		} finally {
			lock.unlock();// 3
		}
	}

下图是DelayQueue.add()的执行示意图。

    如图所示,添加任务分为3大步骤。

    (1)获取Lock。

    (2)添加任务。

            向PriorityQueue添加任务。

            如果在上面2.1中添加的任务是PriorityQueue的头元素,唤醒在Condition中等待的所有线程。

    (3)释放Lock。

3 创建方式

     ScheduledThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建2种类型的ScheduledThreadPoolExecutor,分别是包含若干个线程的ScheduledThreadPoolExecutor和只包含一个线程的SingleThreadScheduledExecutor。

3.1 ScheduledThreadPoolExecutor

    下面是工厂类Executors提供的,创建固定个数线程的ScheduledThreadPoolExecutor的API。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue());
    }
    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

    ScheduledThreadPoolExecutor适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景。

3.2 SingleThreadScheduledExecutor

    下面是Executors提供的,创建单个线程的SingleThreadScheduledExecutor的API。

    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
    public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory 
        threadFactory) {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1, threadFactory));
    }

    SingleThreadScheduledExecutor适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。

本文节选自《Java并发编程的艺术》,版权归原作则所有! 

© 著作权归作者所有

共有 人打赏支持
王胜_淡如止水
粉丝 4
博文 52
码字总数 81235
作品 0
杭州
私信 提问
Java多线程学习(八)线程池与Executor 框架

Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/JavaGuide 历史优质文章推荐: Java并发编程指南专栏 分布式系统的经典基础理...

snailclimb
05/31
0
0
【Java并发专题】27篇文章详细总结Java并发基础知识

努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! github:https://github.com/CL0610/Java-concurrency,欢迎题issue和Pull request。所有的文档都是自己亲自码的,如果觉...

你听___
05/06
0
0
Java程序员从笨鸟到菜鸟全部博客目录【2012年十一月七日更新】

本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh624366188 大学上了一年半,接触java也一年半了,虽然中间也有其他东西的学习,但是还是以java为主路线,想想这一年半,...

长平狐
2012/11/12
103
0
大数据开发培训:0基础学习Java编程语言有哪些知识点?

Java 技术通用、高效、具有平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网等,学习Java首先要知道学习知识点有哪些。在这就用加米谷大数据培训...

加米谷大数据
07/25
0
0
并发编程网线下沙龙

沙龙背景 马上就是并发编程网(ifeve.com)三岁生日了,首先感谢各位读者粉丝对我们的大力支持和鼓励!三年来,我们共组织翻译了600余篇文章,其中Disruptor、NIO、Netty、Storm、并发编程、...

lateron
2014/08/23
41
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring源码学习笔记-1-Resource

打算补下基础,学习下Spring源码,参考书籍是《Spring源码深度解析》,使用版本是Spring 3.2.x,本来想试图用脑图记录的,发现代码部分不好贴,还是作罢,这里只大略记录下想法,不写太细了 ...

zypy333
今天
10
0
RestClientUtil和ConfigRestClientUtil区别说明

RestClientUtil directly executes the DSL defined in the code. ConfigRestClientUtil gets the DSL defined in the configuration file by the DSL name and executes it. RestClientUtil......

bboss
今天
17
0

中国龙-扬科
昨天
2
0
Linux系统设置全局的默认网络代理

更改全局配置文件/etc/profile all_proxy="all_proxy=socks://rahowviahva.ml:80/"ftp_proxy="ftp_proxy=http://rahowviahva.ml:80/"http_proxy="http_proxy=http://rahowviahva.ml:80/"......

临江仙卜算子
昨天
11
0
java框架学习日志-6(bean作用域和自动装配)

本章补充bean的作用域和自动装配 bean作用域 之前提到可以用scope来设置单例模式 <bean id="type" class="cn.dota2.tpye.Type" scope="singleton"></bean> 除此之外还有几种用法 singleton:......

白话
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部