使用具有延迟元素的线程安全列表

Java 9并发编程指南 目录

使用具有延迟元素的线程安全列表

Java API提供了一种在DelayQueue类中实现的可以用在并发应用中的数据结构。在此类中,使用激活日期存储元素,返回或从队列中提取元素的方法将忽略这些元素,这些元素数据将在后续出现 ,它们对这些方法不可见。为了获得这种行为,存储在DelayQueue类中的元素需要实现Delayed接口。这个接口可以使用延迟对象,包含getDelay()方法,返回元素激活之前的时间 。此接口强制实现如下两个方法:

  • compareTo(Delayed o):Delayed接口继承Comparable接口。如果执行此方法的对象延迟小于作为参数传递的对象,则返回小于零的值。如果执行此方法的对象延迟大于作为参数传递的对象,则返回大于零的值。如果两个对象延迟相等,返回0。
  • getDelay(TimeUnit unit):此方法必须返回剩余的时间,直到已过单元参数指定的激活日期。TimeUnit是一个枚举类型的类,包含如下常量:DAYS、HOURS、MICROSECONDS、MILLISECONDS、MINUTES、NANOSECONDS和SECONDS。

在本范例中,通过在DelaydQueue类中存储具有不同激活日期的事件,学习如何使用DelaydQueue类。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤实现范例:

  1. 创建名为Event的类, 指定其实现Delayed接口:

    public class Event implements Delayed {
    
  2. 声明名为startDate的私有Date属性:

    	private final Date startDate;
    
  3. 实现类构造函数,初始化属性:

    	public Event (Date startDate) {
    		this.startDate=startDate;
    	}
    
  4. 实现compareTo()方法,将Delayed对象作为参数接收,返回当前对象与参数之间延迟的差异:

    	@Override
    	public int compareTo(Delayed o) {
    		long result=this.getDelay(TimeUnit.NANOSECONDS)-o.getDelay(TimeUnit.NANOSECONDS);
    		if (result<0) {
    			return -1;
    		} else if (result>0) {
    			return 1;
    		}
    		return 0;
    	}
    
  5. 实现getDelay()方法,返回对象的开始时间与实际时间之间的差异,参数为TimeUnit单位:

    	@Override
    	public long getDelay(TimeUnit unit) {
    		Date now=new Date();
    		long diff=startDate.getTime()-now.getTime();
    		return unit.convert(diff,TimeUnit.MILLISECONDS);
    	}
    }
    
  6. 创建名为Task的类,指定其实现Runnable接口:

    public class Task implements Runnable{
    
  7. 声明名为id的私有int属性,存储标识此任务的数字:

    	private final int id;
    
  8. 声明名为queue的Event类参数化的私有DelayQueue属性:

    	private final DelayQueue<Event> queue;
    
  9. 实现类构造函数,初始化属性:

    	public Task(int id, DelayQueue<Event> queue) {
    		this.id=id;
    		this.queue=queue;
    	}
    
  10. 实现run()方法,首先,计算任务将要创建的事件的激活日期,然后将等于对象ID的秒数添加到实际日期:

    	@Override
    	public void run() {
    		Date now=new Date();
    		Date delay=new Date();
    		delay.setTime(now.getTime()+(id*1000));
    		System.out.printf("Thread %s: %s\n",id,delay);
    
  11. 使用add()方法在队列中存储100个事件:

    		for (int i=0; i<100; i++) {
    			Event event=new Event(delay);
    			queue.add(event);
    		}
    	}
    }
    
  12. 通过创建名为Main的类,添加main()方法,实现本范例主类:

    public class Main {
    	public static void main(String[] args) throws Exception{
    
  13. 创建Event类参数化的DelayQueue对象:

    		DelayQueue<Event> queue=new DelayQueue<>();
    
  14. 创建包含五个线程对象的数组来存储将要执行的任务:

    		Thread threads[]=new Thread[5];
    
  15. 创建五个不同ID的Task对象:

    		for (int i=0; i<threads.length; i++){
    			Task task=new Task(i+1, queue);
    			threads[i]=new Thread(task);
    		}
    
  16. 启动已创建的五个任务:

    		for (int i=0; i<threads.length; i++) {
    			threads[i].start();
    		}
    
  17. 使用join()方法等到线程执行结束:

    		for (int i=0; i<threads.length; i++) {
    			threads[i].join();
    		}
    
  18. 输出存储在队列中的元素到控制台。当队列长度大于零时,使用poll()方法得到Event类。如果返回null,设置主线程休眠500毫秒等到更多事件激活:

    		do {
    			int counter=0;
    			Event event;
    			do {
    				event=queue.poll();
    				if (event!=null) counter++;
    			} while (event!=null);
    				System.out.printf("At %s you have read %d events\n", new Date(), counter);
    				TimeUnit.MILLISECONDS.sleep(500);
    		} while (queue.size()>0);
    	}
    }
    

工作原理

本节中,我们实现了Event类,此类包含一个唯一属性,事件的激活日期,且实现Delayed接口,可以在DelayQueue类中存储Event对象。

getDelay()方法返回激活日期和实际日期之间的纳秒数。两个日期军事Date类对象,使用getTime()方法返回转换为毫秒的日期。然后,将这个值转换成作为参数接收的TimeUnit。DelayQueue类的工作时间是毫微秒,在这一点上它是透明的。

如果执行compareTo()方法的对象延迟小于作为参数传递的对象,则返回小于零的值。如果延迟大于作为参数传递的对象,则返回大于零的值。如果两个对象延迟相等,返回0。

我们还实现了Task类,包含名为id的integer属性。当执行任务对象时,它将等于任务ID的秒数添加到实际日期,这个日期是此任务在DelayQueue类中存储的事件的激活日期。每个任务对象使用add()方法在队列中存储100个事件。

最后,在Main类的main()方法中,创建五个Task对象并且在对应的线程中执行。当这些线程完成执行时,使用poll()方法输出所有事件到控制台。此方法检索并删除队列的第一个元素 ,如果队列没有任何活动元素,则返回null值。在调用poll()方法时,如果返回Event类,则增加计数器。当此方法返回null值时,输出计数器值到控制台,且设置线程休眠半秒钟来等待更多的活动时间。当获得500个存储在队列中的事件时,结束程序执行。

下图显示本范例在控制台输出的部分执行信息:

可以看到当程序被激活时,如何只得到100个事件。

必须谨慎使用size()方法,此方法返回列表中元素的总数,包括活动和非活动元素。

扩展学习

DelayQueue类还有如下一些方法:

  • clear():此方法删除队列的所有元素。
  • Offer(E e):这里E表示用于参数化PriorityBlockingQueue类的类,它将作为参数传入的元素插入到队列中。
  • peek():此方法检索但不删除队列的第一个元素。
  • take():此方法检索且删除队列第一个元素,如果队列没有活动元素,执行此方法的线程将被阻塞,直到线程得到活动元素。

更多关注

  • 本章“使用阻塞线程安全双端队列”小节
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值