ScheduledThreadPoolExecutor

该类继承自ThreadPoolExecutor,增加了定时执行线程和延迟启动的功能,这两个功能是通过延时队列DelayedWorkQueue辅助实现的。

线程池里面的线程需要从队列里面获取任务,任务根据延时时长是有顺序的,线程池的线一直获取延时最短的任务,也就是最小二叉堆中的堆顶元素,这个时候堆顶元素成为各个线程争夺的资源,

  1. 在获取堆顶元素的时候加锁(ReentrabtLock,可重入,独占锁),这样获取到锁的线程开始获取堆顶元素,其他线程在不能获取锁被阻塞
  2. 如果堆顶元素的延时还没有到,当前线程成为leader线程,进入超时等待

    1.1. 这个时候其他被阻塞的线程有机会获取锁

    1.2. 获取锁的线程发现leader线程已经另有其人(leader != null)

    1.3. 线程进入等待,available.await();
  3. 如果线程等待正常结束(时间已到),让出leander地位,再次进入循环,发现delayed <= 0,获取对顶元素,并重新堆化筛选出堆顶元素,调用available.signal()唤醒等待的线程(比如1.1的情况),释放锁

    3.1 假设是1.1里面的线程被唤醒(实际不一定,唤醒的也可能是其他线程)

    3.2 重复1、2、3、4的流程
  4. 线程获取到任务开始运行,运行ScheduledFutureTask.run方法,如果是定时任务的话,会重新计算延时时间,将任务加入队列,等待下次运行

DelayedWorkQueue

这个队列是一个阻塞的队列,队列基于二叉堆实现的,根据线程距离下次运行的时间比较大小,所以添加和删除元素都是二叉堆的重新堆化

offer

put、add都是调用下面的offer方法

public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture e = (RunnableScheduledFuture)x;
final ReentrantLock lock = this.lock;
// 获取锁
lock.lock();
try {
int i = size;
if (i >= queue.length)
// 如果队列已满进行扩容
grow();
size = i + 1;
if (i == 0) {
// 第一个元素直接入队
queue[0] = e;
setIndex(e, 0);
} else {
// 加入新元素重新堆化
siftUp(i, e);
}
if (queue[0] == e) {
// 如果原来队列为空,说明可能有线程在等待,所以唤醒一个线程
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
} // 队列扩容,每次增加50%,直到Integer的最大值
private void grow() {
int oldCapacity = queue.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
if (newCapacity < 0) // overflow
newCapacity = Integer.MAX_VALUE;
queue = Arrays.copyOf(queue, newCapacity);
} // 新加入元素之后重新堆化,最小堆
private void siftUp(int k, RunnableScheduledFuture key) {
while (k > 0) {
// 二叉堆的特性父节点的序号 = (当前节点序号 - 1) / 2
int parent = (k - 1) >>> 1;
RunnableScheduledFuture e = queue[parent];
// 找到新加入元素合适的位置
if (key.compareTo(e) >= 0)
break;
queue[k] = e;
setIndex(e, k);
k = parent;
}
// 新元素入队列
queue[k] = key;
setIndex(key, k);
}

take

take的时候使用的是leader-follow模式,只有一个leader,其他都是follow,在每次finishPoll的时候都会选举出新的

public RunnableScheduledFuture take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture first = queue[0];
if (first == null)
// 队列为空则进入等待
available.await();
else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay <= 0)
// 延迟或者定时时间(其实定时间也是一种延迟)到,从队列中取出任务执行
return finishPoll(first);
// 如果leader != null 说明leader是另外的线程(有可能是leader线程在available.awaitNanos(delay))是leader,那么当前线程进入等待
else if (leader != null)
available.await();
else {
// 没有leader线程的时候,当前线程成为新的leader
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 这里进行超时等待,超过delay之后就会恢复运行,或者是被其他线程唤醒
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
// 重置leader以便进入下一次循环
leader = null;
}
}
}
}
} finally {
// 队列不为空的时候发出signal,leader == null的条件是防止leader线程在available.awaitNanos(delay)的时候被唤醒
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
} // 返回第一个等待的线程(延时已到),并将剩余元素再次堆化
private RunnableScheduledFuture finishPoll(RunnableScheduledFuture f) {
int s = --size;
RunnableScheduledFuture x = queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
setIndex(f, -1);
return f;
} // 因为key是原来堆中的元素位于堆得最底层,key本来就是较大的元素,
private void siftDown(int k, RunnableScheduledFuture key) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
RunnableScheduledFuture c = queue[child];
int right = child + 1;
if (right < size && c.compareTo(queue[right]) > 0)
c = queue[child = right];
if (key.compareTo(c) <= 0)
// 找到key的位置,大于父节点,小于子节点
break;
queue[k] = c;
setIndex(c, k);
k = child;
}
queue[k] = key;
setIndex(key, k);
}

问题

period线程怎么实现定时调用

setNextRunTime会重新计算下次运行需要等待的时间,因为period线程运行完后已经从队列中删除,在reExecutePeriodic方法中会重新进入队列,调用ensurePrestart重新开始执行任务

public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
// 非定时线程调用FutureTask的run方法
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) { // 定时线程调用FutureTask的runAndReset方法
// 设置下次运行时间
setNextRunTime();
// 重新准备运行
reExecutePeriodic(outerTask);
}
} void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
// task进入队列
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
} void ensurePrestart() {
int wc = workerCountOf(ctl.get());
// 进入线程池等待运行,接下来就和ThreadPoolExecutor运行顺序一样了
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}

线程运行完成之后任务会不会从队列中删除,怎么删除的?

会删除,在finnishPoll中,重新堆化选出堆顶元素,原来的堆顶元素被覆盖,也就是删除了

Java 线程 — ScheduledThreadPoolExecutor的更多相关文章

  1. 深入理解Java线程池:ScheduledThreadPoolExecutor

    介绍 自JDK1.5开始,JDK提供了ScheduledThreadPoolExecutor类来支持周期性任务的调度.在这之前的实现需要依靠Timer和TimerTask或者其它第三方工具来完成.但T ...

  2. 【Java 多线程】Java线程池类ThreadPoolExecutor、ScheduledThreadPoolExecutor及Executors工厂类

    Java中的线程池类有两个,分别是:ThreadPoolExecutor和ScheduledThreadPoolExecutor,这两个类都继承自ExecutorService.利用这两个类,可以创建 ...

  3. 【转载】 Java线程面试题 Top 50

    Java线程面试题 Top 50 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员 的欢迎.大多数待遇丰厚的J ...

  4. Java线程新特征——Java并发库

    一.线程池   Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定 ...

  5. Java线程池使用说明

    Java线程池使用说明 转自:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极 ...

  6. Java线程面试题 Top 50 (转载)

    转载自:http://www.cnblogs.com/dolphin0520/p/3958019.html 原文链接:http://www.importnew.com/12773.html   本文由 ...

  7. JAVA线程池原理详解二

    Executor框架的两级调度模型 在HotSpot VM的模型中,JAVA线程被一对一映射为本地操作系统线程.JAVA线程启动时会创建一个本地操作系统线程,当JAVA线程终止时,对应的操作系统线程也 ...

  8. 四种Java线程池用法解析

    本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...

  9. 50 道 Java 线程面试题(转载自牛客网)

    下面是 Java 线程相关的热门面试题,你可以用它来好好准备面试. 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理 ...

随机推荐

  1. gem sources --add http://ruby.taobao.org/

    gem sources  gem sources  gem sources --remove http://ruby.taobao.org/ gem sources --add http://ruby ...

  2. 高德地图API编译警告

    版本: V2.1.1 警告内容: (null): warning: (i386) /Users/xiaominghan/Desktop/autonavi/MAMapKit_3D_r923_201310 ...

  3. IntelliJ IDEA 发布最新版本13.0.1

    难闻转自:慧都控件网 值得高兴的消息,IntelliJ IDEA v13.0.1目前已经发布,相对于IntelliJ IDEA v13而言,此次更新内容,是略微改进和提高了性能,如代码输入变化,及完善 ...

  4. Navicat Premium下sql导入中文乱码解决方案

    今天帮忙朋友找bug的时候,准备导入她数据库里面的数据,所以我就试图在mysql管理工具Navicat下面导入相应的mysql命令.结果发现导入的中文字符全部变成乱码,所以做了如下这种尝试: 在“连接 ...

  5. (转)如何为你的Viewcontroller瘦身

    View controllers are often the biggest files in iOS projects, and they often contain way more code t ...

  6. iOS直播直播,头都大了

    随着直播市场的火热,市场大军都逐步进入直播市场 ,腾讯旗下的NOW直播也不例外 先说说直播设计底层 一 .流媒体 1 - 伪流媒体 1.1 扫盲:边下载边播放 1.2 伪流媒体:视频不是实时播放的,先 ...

  7. 利用HtmlAgilityPack库进行HTML数据抓取

    主要介绍基于XPATH的文本分析方式的实现,代码如下: using System; using System.Collections.Generic; using System.Linq; using ...

  8. 打开mysql时,提示 1040,Too many connections

    打开mysql时,提示 1040,Too many connections,这样就无法打开数据库,看不了表里边的内容了. 出现这个问题的原因是,同时对数据库的连接数过大,mysql默认的最大连接数是1 ...

  9. 《你必须知道的.NET》读书笔记:从Hello World认识IL

    通用的语言基础是.NET运行的基础,当我们对程序运行的结果有异议的时候,如何透过本质看表面,需要我们从底层来入手探索,这时候,IL便是我们必须知道的基础. 一.IL基础概念 1.1 什么是IL? IL ...

  10. 【读书笔记】-- JavaScript数组

    数组是一段线性分配的内存,它通过整数计算偏移并访问其中的元素.大多数的语言都会要求一个数组的元素是相同类型,但JavaScript数组可以包含任意类型. var misc = ['string', n ...