并发集合(四)用优先级对使用阻塞线程安全的列表排序

简介:

声明:本文是《 Java 7 Concurrency Cookbook 》的第六章,作者: Javier Fernández González     译者:许巧辉 校对:方腾飞

用优先级对使用阻塞线程安全的列表排序

一个典型的需求是,当你需要使用一个有序列表的数据结构时,Java提供的PriorityBlockingQueue类就拥有这种功能。

你想要添加到PriorityBlockingQueue中的所有元素必须实现Comparable接口。这个接口有一个compareTo()方法,它接收同样类型的对象,你有两个比较的对象:一个是执行这个方法的对象,另一个是作为参数接收的对象。如果本地对象小于参数,则该方法返回小于0的数值。如果本地对象大于参数,则该方法返回大于0的数值。如果本地对象等于参数,则该方法返回等于0的数值。

PriorityBlockingQueue使用compareTo()方法决定插入元素的位置。(校注:默认情况下)较大的元素将被放在队列的尾部。

阻塞数据结构(blocking data structure)是PriorityBlockingQueue的另一个重要特性。它有这样的方法,如果它们不能立即进行它们的操作,则阻塞这个线程直到它们的操作可以进行。

在这个指南中,你将学习如何使用PriorityBlockingQueue类实现一个例子,你将在相同的列表上使用不同的优先级存储大量事件(event),然后检查队列的排序是否是你想要的。

准备工作…

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

如何做…

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

1.实现Event类,并指定它实现参数化为Event类的Comparable接口。

1 public class Event implements Comparable<Event> {

2.声明一个私有的、int类型的属性thread,用来存储已创建事件的线程数。

1 private int thread;

3.声明一个私有的、int类型的属性priority,用来存储事件的优先级。

1 private int priority;

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

1 public Event(int thread, int priority){
2 this.thread=thread;
3 this.priority=priority;
4 }

5.实现getThread()方法,用来返回thread属性的值。

1 public int getThread() {
2 return thread;
3 }

6.实现getPriority()方法,用来返回priority属性的值。

1 public int getPriority() {
2 return priority;
3 }

7.实现compareTo()方法。它接收Event作为参数,并且比较当前事件与参数的优先级。如果当前事件的优先级更大,则返回-1,如果这两个优先级相等,则返回0,如果当前事件的优先级更小,则返回1。注意,这与大多数Comparator.compareTo()的实现是相反的。

01 @Override
02 public int compareTo(Event e) {
03 if (this.priority>e.getPriority()) {
04 return -1;
05 else if (this.priority<e.getPriority()) {
06 return 1;
07 else {
08 return 0;
09 }
10 }

8.创建一个Task类,并指定它实现Runnable接口。

1 public class Task implements Runnable {

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

1 private int id;

10.声明一个私有的、参数化为Event类的PriorityBlockingQueue类型的属性queue,用来存储任务产生的事件。

1 private PriorityBlockingQueue<Event> queue;

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

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

12.实现run()方法。它存储100个事件到队列,使用它们的ID来标识创建事件的任务,并给予不断增加的数作为优先级。使用add()方法添加事件到队列中。

1 @Override
2 public void run() {
3 for (int i=0; i<1000; i++){
4 Event event=new Event(id,i);
5 queue.add(event);
6 }
7 }

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

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

14.创建一个参数化为Event类的PriorityBlockingQueue对象。

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

15.创建一个有5个Thread对象的数组,用来存储执行5个任务的线程。

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

16.创建5个Task对象。存储前面创建的线程数组。

1 for (int i=0; i<taskThreads.length; i++){
2 Task task=new Task(i,queue);
3  
4 taskThreads[i]=new Thread(task);
5 }

17.启动前面创建的5个线程。

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

18.使用join()方法,等待这5个线程的结束。

1 for (int i=0; i<taskThreads.length ; i++) {
2 try {
3 taskThreads[i].join();
4 catch (InterruptedException e) {
5 e.printStackTrace();
6 }
7 }

19.将列队真实大小和存储在它里面的事件写入到控制台。使用poll()方法从队列中取出事件。

1 System.out.printf("Main: Queue Size: %d\n",queue.size());
2 for (int i=0; i<taskThreads.length*1000; i++){
3 Event event=queue.poll();
4 System.out.printf("Thread %s: Priority %d\n",event.
5 getThread(),event.getPriority());
6 }

20.将队列最后的大小写入到控制台。

1 System.out.printf("Main: Queue Size: %d\n",queue.size());
2 System.out.printf("Main: End of the program\n");

它是如何工作的…

在这个指南中,你已使用PriorityBlockingQueue实现Event对象的一个优先级队列。正如我们在引言中提到的,所有存储在PriorityBlockingQueue的元素必须实现Comparable接口,所以,你已在Event类中实现compareTo()方法。

所有事件都有一个优先级属性。拥有更高优先级的元素将成为队列的第一个元素。当你已实现compareTo()方法,如果执行这个方法的事件拥有比作为参数传入的事件更高的优先级时,它将返回-1。在其他情况下,如果执行这个方法的事件拥有比作为参数传入的事件更低的优先级时,它将返回1。如果这两个对象拥有相同优先级,compareTo()方法将返回0。在这种情况下,PriorityBlockingQueue类并不能保证元素的顺序。

我们已实现Task类来添加Event对象到优先级队列中。每个任务对象使用add()方法往队列添加1000个事件(0到99种优先级)。

Main类的main()方法创建5个Task对象,并用相应的线程执行它们。当所有的线程完成它们的执行,你已将所有元素写入到控制台。我们使用poll()方法从队列中获取元素。这个方法返回并删除队列的第一个元素。

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

2

你可以看出这个队列如何有5000个元素,第一个元素如何拥有最大的优先级值。

不止这些…

PriorityBlockingQueue类提供其他有趣的方法,以下是其中一些方法的描述:

  • clear():这个方法删除队列中的所有元素。
  • take():这个方法返回并删除队列中的第一个元素。如果队列是空的,这个方法将阻塞线程直到队列有元素。
  • put(E e):E是用来参数化PriorityBlockingQueue类的类。这个方法将作为参数传入的元素插入到队列中。
  • peek():这个方法返回列队的第一个元素,但不删除它。

参见

目录
相关文章
|
22天前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
8天前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
8天前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
12天前
|
Rust 并行计算 安全
揭秘Rust并发奇技!线程与消息传递背后的秘密,让程序性能飙升的终极奥义!
【8月更文挑战第31天】Rust 以其安全性和高性能著称,其并发模型在现代软件开发中至关重要。通过 `std::thread` 模块,Rust 支持高效的线程管理和数据共享,同时确保内存和线程安全。本文探讨 Rust 的线程与消息传递机制,并通过示例代码展示其应用。例如,使用 `Mutex` 实现线程同步,通过通道(channel)实现线程间安全通信。Rust 的并发模型结合了线程和消息传递的优势,确保了高效且安全的并行执行,适用于高性能和高并发场景。
26 0
|
21天前
|
Java 开发者
【编程高手必备】Java多线程编程实战揭秘:解锁高效并发的秘密武器!
【8月更文挑战第22天】Java多线程编程是提升软件性能的关键技术,可通过继承`Thread`类或实现`Runnable`接口创建线程。为确保数据一致性,可采用`synchronized`关键字或`ReentrantLock`进行线程同步。此外,利用`wait()`和`notify()`方法实现线程间通信。预防死锁策略包括避免嵌套锁定、固定锁顺序及设置获取锁的超时。掌握这些技巧能有效增强程序的并发处理能力。
17 2
|
22天前
|
安全 Java
【Java集合类面试十三】、HashMap如何实现线程安全?
实现HashMap线程安全的方法包括使用Hashtable类、ConcurrentHashMap,或通过Collections工具类将HashMap包装成线程安全的Map。
|
11天前
|
开发框架 Android开发 iOS开发
跨平台开发的双重奏:Xamarin在不同规模项目中的实战表现与成功故事解析
【8月更文挑战第31天】在移动应用开发领域,选择合适的开发框架至关重要。Xamarin作为一款基于.NET的跨平台解决方案,凭借其独特的代码共享和快速迭代能力,赢得了广泛青睐。本文通过两个案例对比展示Xamarin的优势:一是初创公司利用Xamarin.Forms快速开发出适用于Android和iOS的应用;二是大型企业借助Xamarin实现高性能的原生应用体验及稳定的后端支持。无论是资源有限的小型企业还是需求复杂的大公司,Xamarin均能提供高效灵活的解决方案,彰显其在跨平台开发领域的强大实力。
20 0
【多线程面试题十二】、阻塞线程的方式有哪些?
线程阻塞的方式包括调用sleep()方法、阻塞式IO操作、等待同步监视器的获取、等待通知(notify),以及慎用的suspend()方法。
|
16天前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
43 1
|
8天前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。