并发集合(五)使用线程安全的、带有延迟元素的列表

简介:

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

DelayedQueue类是Java API提供的一种有趣的数据结构,并且你可以用在并发应用程序中。在这个类中,你可以存储带有激活日期的元素。方法返回或抽取队列的元素将忽略未到期的数据元素。它们对这些方法来说是看不见的。

为了获取这种行为,你想要存储到DelayedQueue类中的元素必须实现Delayed接口。这个接口允许你处理延迟对象,所以你将实现存储在DelayedQueue对象的激活日期,这个激活时期将作为对象的剩余时间,直到激活日期到来。这个接口强制实现以下两种方法:

  • compareTo(Delayed o):Delayed接口继承Comparable接口。如果执行这个方法的对象的延期小于作为参数传入的对象时,该方法返回一个小于0的值。如果执行这个方法的对象的延期大于作为参数传入的对象时,该方法返回一个大于0的值。如果这两个对象有相同的延期,该方法返回0。
  • getDelay(TimeUnit unit):该方法返回与此对象相关的剩余延迟时间,以给定的时间单位表示。TimeUnit类是一个枚举类,有以下常量:DAYS、HOURS、 MICROSECONDS、MILLISECONDS、 MINUTES、 NANOSECONDS 和 SECONDS。


在这个例子中,你将学习如何使用DelayedQueue类来存储一些具有不同激活日期的事件。

准备工作…

这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。

如何做…

按以下步骤来实现的这个例子:

1.创建一个实现Delayed接口的Event类。


1 public class Event implements Delayed

2.声明一个私有的、Date类型的属性startDate。


1 private Date startDate;

3.实现这个类的构造器,并初始化它的属性。


1 public Event (Date startDate) {this.startDate=startDate;}

4.实现compareTo()方法。它接收一个Delayed对象作为参数。返回当前对象的延期与作为参数传入对象的延期之间的差异。


1 <br /><br />@Override<br />public int compareTo(Delayed o) {<br />long result=this.getDelay(TimeUnit.NANOSECONDS)-o.<br />getDelay(TimeUnit.NANOSECONDS);<br />if (result&lt;0) {<br />return -1;<br />} else if (result&gt;0) {<br />return 1;<br />}<br />return 0;<br />}<br /><br />

5.实现getDelay()方法。返回对象的startDate与作为参数接收的TimeUnit的真实日期之间的差异。


1 public long getDelay(TimeUnit unit) {
2 Date now=new Date();
3 long diff=startDate.getTime()-now.getTime();
4 return unit.convert(diff,TimeUnit.MILLISECONDS);
5 }

6.创建一个实现Runnable接口的Task类。


1 public class Task implements Runnable {

7.声明一个私有的、int类型的属性id,用来存储任务的标识数字。


1 private int id;

8.声明一个私有的、参数化为Event类的DelayQueue类型的属性queue。


1 <br /><br />private DelayQueue&lt;Event&gt; queue;<br /><br />

9.实现这个类的构造器,并初始化它的属性。


1 public Task(int id, DelayQueue<Event> queue) {
2 this.id=id;<br />this.queue=queue;
3 }

10.实现run()方法。首先,计算任务将要创建的事件的激活日期。添加等于对象ID的实际日期秒数。


1 @Override
2 public void run() {
3 Date now=new Date();
4 Date delay=new Date();
5 delay.setTime(now.getTime()+(id*1000));
6 System.out.printf("Thread %s: %s\n",id,delay);

11.使用add()方法,在队列中存储100个事件。


1 for (int i=0; i&lt;100; i++) {
2 Event event=new Event(delay);
3 queue.add(event);
4 }

12.通过创建Main类,并实现main()方法,来实现这个例子的主类。


1 public class Main {
2 public static void main(String[] args) throws Exception {

13.创建一个参数化为Event类的DelayedQueue对象。


1 DelayQueue<Event> queue=new DelayQueue<>();

14.创建一个有5个Thread对象的数组,用来存储将要执行的任务。


1 Thread threads[]=new Thread[5];

15.创建5个具有不同IDs的Task对象。


1 for (int i=0; i&lt;threads.length; i++){<br />Task task=new Task(i+1, queue);<br />threads[i]=new Thread(task);
2 }

16.开始执行前面创建的5个任务。


1 for (int i=0; i<threads.length; i++) {
2 threads[i].start();
3 }

17.使用join()方法等待任务的结束。


1 for (int i=0; i<threads.length; i++) {
2 threads[i].join();
3 }

18.将存储在队列中的事件写入到控制台。当队列的大小大于0时,使用poll()方法获取一个Event类。如果它返回null,令主线程睡眠500毫秒,等待更多事件的激活。


01 do {
02 int counter=0;
03 Event event;
04 do {
05 event=queue.poll();
06 if (event!=null) counter++;
07 } while (event!=null);
08 System.out.printf("At %s you have read %d events\n",new Date(),counter);
09 TimeUnit.MILLISECONDS.sleep(500);
10 }while (queue.size()>0);
11 }
12 }

它是如何工作的…

在这个指南中,我们已实现Event类。这个类只有一个属性(表示事件的激活日期),实现了Delayed接口,所以,你可以在DelayedQueue类中存储Event对象。

getDelay()方法返回在实际日期和激活日期之间的纳秒数。这两个日期都是Date类的对象。你已使用getTime()方法返回一个被转换成毫秒的日期,你已转换那个值为作为参数接收的TimeUnit。DelayedQueue类使用纳秒工作,但这一点对于你来说是透明的。

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

你同时实现了Task类。这个类有一个整数属性id。当一个Task对象被执行,它增加一个等于任务ID的秒数作为实际日期,这是被这个任务存储在DelayedQueue类的事件的激活日期。每个Task对象使用add()方法存储100个事件到队列中。

最后,在Main类的main()方法中,你已创建5个Task对象,并用相应的线程来执行它们。当这些线程完成它们的执行,你已使用poll()方法将所有元素写入到控制台。这个方法检索并删除队列的第一个元素。如果队列中没有任务到期的元素,这个方法返回null值。你调用poll()方法,并且如果它返回一个Evnet类,你增加计数器。当poll()方法返回null值时,你写入计数器的值到控制台,并且令线程睡眠半秒等待更多的激活事件。当你获取存储在队列中的500个事件,这个程序执行结束。

以下截图显示程序执行的部分输出:

3

你可以看出这个程序当它被激活时,只获取100个事件。

注意:你必须十分小心size()方法。它返回列表中的所有元素数量,包含激活与未激活元素。

不止这些…

DelayQueue类提供其他有趣方法,如下:

  • clear():这个方法删除队列中的所有元素。
  • offer(E e):E是代表用来参数化DelayQueue类的类。这个方法插入作为参数传入的元素到队列中。
  • peek():这个方法检索,但不删除队列的第一个元素。
  • take():这具方法检索并删除队列的第一个元素。如果队列中没有任何激活的元素,执行这个方法的线程将被阻塞,直到队列有一些激活的元素。
目录
相关文章
|
23天前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
9天前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
9天前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
13天前
|
Rust 并行计算 安全
揭秘Rust并发奇技!线程与消息传递背后的秘密,让程序性能飙升的终极奥义!
【8月更文挑战第31天】Rust 以其安全性和高性能著称,其并发模型在现代软件开发中至关重要。通过 `std::thread` 模块,Rust 支持高效的线程管理和数据共享,同时确保内存和线程安全。本文探讨 Rust 的线程与消息传递机制,并通过示例代码展示其应用。例如,使用 `Mutex` 实现线程同步,通过通道(channel)实现线程间安全通信。Rust 的并发模型结合了线程和消息传递的优势,确保了高效且安全的并行执行,适用于高性能和高并发场景。
28 0
|
21天前
|
Java 开发者
【编程高手必备】Java多线程编程实战揭秘:解锁高效并发的秘密武器!
【8月更文挑战第22天】Java多线程编程是提升软件性能的关键技术,可通过继承`Thread`类或实现`Runnable`接口创建线程。为确保数据一致性,可采用`synchronized`关键字或`ReentrantLock`进行线程同步。此外,利用`wait()`和`notify()`方法实现线程间通信。预防死锁策略包括避免嵌套锁定、固定锁顺序及设置获取锁的超时。掌握这些技巧能有效增强程序的并发处理能力。
17 2
|
23天前
|
安全 Java
【Java集合类面试十三】、HashMap如何实现线程安全?
实现HashMap线程安全的方法包括使用Hashtable类、ConcurrentHashMap,或通过Collections工具类将HashMap包装成线程安全的Map。
|
23天前
|
Java
【Java集合类面试十二】、HashMap为什么线程不安全?
HashMap在并发环境下执行put操作可能导致循环链表的形成,进而引起死循环,因而它是线程不安全的。
|
12天前
|
开发框架 Android开发 iOS开发
跨平台开发的双重奏:Xamarin在不同规模项目中的实战表现与成功故事解析
【8月更文挑战第31天】在移动应用开发领域,选择合适的开发框架至关重要。Xamarin作为一款基于.NET的跨平台解决方案,凭借其独特的代码共享和快速迭代能力,赢得了广泛青睐。本文通过两个案例对比展示Xamarin的优势:一是初创公司利用Xamarin.Forms快速开发出适用于Android和iOS的应用;二是大型企业借助Xamarin实现高性能的原生应用体验及稳定的后端支持。无论是资源有限的小型企业还是需求复杂的大公司,Xamarin均能提供高效灵活的解决方案,彰显其在跨平台开发领域的强大实力。
20 0
|
17天前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
43 1
|
2天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
17 10