文档章节

Java并发新构件之DelayQueue

摆渡者
 摆渡者
发布于 2015/10/13 18:30
字数 868
阅读 2817
收藏 14

    DelayQueue主要用于放置实现了Delay接口的对象,其中的对象只能在其时刻到期时才能从队列中取走。这种队列是有序的,即队头的延迟到期时间最短。如果没有任何延迟到期,那么久不会有任何头元素,并且poll()将返回null(正因为这样,你不能将null放置到这种队列中)

    下面是一个示例,其中的Delayed对象自身就是任务,而DelayedTaskConsumer将最“紧急”的任务从队列中取出来,然后运行它:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.*;

class DelayedTask implements Runnable, Delayed {

    private static int counter = 0;
    protected static List<DelayedTask> sequence = new ArrayList<>();
    private final int id = counter++;
    private final int delayTime;
    private final long triggerTime;
    public DelayedTask(int delayInMillis) {
        delayTime = delayInMillis;
        triggerTime = System.nanoTime() + NANOSECONDS.convert(delayTime, MILLISECONDS);
        sequence.add(this);
    }
    
    @Override
    public int compareTo(Delayed o) {
        DelayedTask that = (DelayedTask)o;
        if (triggerTime < that.triggerTime) return -1;
        if (triggerTime > that.triggerTime) return 1;
        return 0;
    }

    /**
     * 剩余的延迟时间
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(triggerTime - System.nanoTime(), NANOSECONDS);
    }

    @Override
    public void run() {
        System.out.println(this + " ");
    }
    
    @Override
    public String toString() {
        return String.format("[%1$-4d]", delayTime) + " Task " + id;
    }
    
    public static class EndSentinel extends DelayedTask {
        private ExecutorService exec;
        public EndSentinel(int delay, ExecutorService exec) {
            super(delay);
            this.exec = exec;
        }
        @Override
        public void run() {
            System.out.println(this + " calling shutDownNow()");
            exec.shutdownNow();
        }
    }
}

class DelayedTaskConsumer implements Runnable {
    private DelayQueue<DelayedTask> tasks;
    public DelayedTaskConsumer(DelayQueue<DelayedTask> tasks) {
        this.tasks = tasks;
    }
    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                tasks.take().run();//run tasks with current thread.
            }
        } catch (InterruptedException e) {
            // TODO: handle exception
        }
        System.out.println("Finished DelaytedTaskConsumer.");
    }
}


public class DelayQueueDemo {
    public static void main(String[] args) {
        int maxDelayTime = 5000;//milliseconds
        Random random = new Random(47);
        ExecutorService exec = Executors.newCachedThreadPool();
        DelayQueue<DelayedTask> queue = new DelayQueue<>();
        //填充10个休眠时间随机的任务
        for (int i = 0; i < 10; i++) {
            queue.put(new DelayedTask(random.nextInt(maxDelayTime)));
        }
        //设置结束的时候。
        queue.add(new DelayedTask.EndSentinel(maxDelayTime, exec));
        exec.execute(new DelayedTaskConsumer(queue));
    }
}

执行结果:

[200 ] Task 7 
[429 ] Task 5 
[555 ] Task 1 
[961 ] Task 4 
[1207] Task 9 
[1693] Task 2 
[1861] Task 3 
[4258] Task 0 
[4522] Task 8 
[4868] Task 6 
[5000] Task 10 calling shutDownNow()
Finished DelaytedTaskConsumer.

    DelayedTask包含一个称为sequence的List<DelayedTask>,它保存了在任务被创建的顺序,因此我们可以看到排序是按照实际发生的顺序执行的(即到期时间短的先出队列)。

    Delayed接口有一个方法名为getDelay(),它可以用来告知延迟到期还有多长时间,或者延迟在多长时间之前已经到期。这个方法将强制我们去使用TimeUnit类,因为这就是参数类型。这会产生一个非常方便的类,因为你可以很容易地转换单位而无需做任何声明。例如,delayTime的值是以毫秒为单位的,但是System.nanoTime()产生的时间则是以纳秒为单位的。你可以转换delayTime的值,方法是声明它的单位以及你希望以什么单位来表示,就像下面这样:

NANOSECONDS.convert(delayTime, MILLISECONDS);

    为了排序,Delayed接口还继承了Comparable接口,因此必须实现compareTo()方法,使其可以产生合理的比较。toString()则提供了输出格式化,而嵌套的EndSentinel类提供了一种关闭所有事物的途径,具体做法是将其放置为队列的最后一个元素。

    注意,因为DelayedTaskConsumer自身是一个任务,所以它有自己的Thread,它可以使用这个线程来运行从队列中获取的所有任务。由于任务是按照队列优先级的顺序来执行的,因此在本例中不需要启动任何单独的线程来运行DelayedTask。

© 著作权归作者所有

共有 人打赏支持
摆渡者
粉丝 326
博文 171
码字总数 205876
作品 0
浦东
程序员
Java并发编程利用 Condition 实现阻塞队列

什么是阻塞队列 BlockingQueue 队列是一种数据结构,它的特点是先进先出(First In First Out),它有两个基本操作:在队列尾部加入一个元素,从队列头部移除一个元素。队列在多线程应用中,...

行走在旅途中
2017/11/07
0
0
读书笔记之《Java并发编程的艺术》-并发编程容器和框架(重要)

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
0
1
【死磕Java并发】—– 死磕 Java 并发精品合集

【死磕 Java 并发】系列是 LZ 在 2017 年写的第一个死磕系列,一直没有做一个合集,这篇博客则是将整个系列做一个概览。 先来一个总览图: 【高清图,请关注“Java技术驿站”公众号,回复:脑...

chenssy
07/22
0
0
Java线程框架_Executor

Executor 框架是 juc 里提供的线程池的实现。前两天看了下 Executor 框架的一些源码,做个简单的总结。 线程池大概的思路是维护一个的线程池用于执行提交的任务。我理解池的技术的主要意义有...

天呀鲁哇
2015/02/05
0
0
Java多线程学习(八)线程池与Executor 框架

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

snailclimb
05/31
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Redis常用命令

keys 我把这个命令放在第一位,是因为笔者曾经做过的项目,以及一些朋友的项目,都因为使用keys这个命令,导致出现性能毛刺。这个命令的时间复杂度是O(N),而且redis又是单线程执行,在执行k...

谢思华
19分钟前
1
0
关于css宽度分离

所谓宽度分离就是width 属性不与影响宽度的 padding/border(有时候包括 margin)属性共存 例如: .box{width:200px;padding:20px;border:1px solid;} 为何要做宽度分离 一说到分离就是为了好...

莫西摩西
30分钟前
0
0
Linux常用命令

###############常用命令说明############################## cat /proc/version 显示内核的版本 mv dir1 new_dir 重命名/移动 一个目录 rm -rf a.txt b.txt c.txt 删除多个文件 chmod 777 ......

lyle_luo
37分钟前
1
0
全国地区代码科普

全国地区代码表 天津市 地区代码 地区名称 1100 天津市 辽宁省 地区代码 地区名称 2210 沈阳市 2210 法库县 2210 康平县 2210 辽中县 2210 新民市 2220 大连市 2222 普兰店市 2223 庄河市 22...

恋码之子
37分钟前
0
0
DbForge Schema Compare for MySQL入门教程:生成比较报告

【dbForge Schema Compare for MySQL下载】 当架构比较完成后,您可以生成比较报告以保留架构更改的记录。 1. 在“Comparison” 菜单中,单击“Generate Comparison Report” 。将打开“Gen...

Miss_Hello_World
38分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部