Java中的5大队列,你知道几个?

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

本文已收录至 https://github.com/vipstone/algorithm 《算法图解》系列。

通过前面文章的学习《一文详解「队列」,手撸队列的3种方法!》我们知道了队列(Queue)是先进先出(FIFO)的,并且我们可以用数组、链表还有 List 的方式来实现自定义队列,那么本文我们来系统的学习一下官方是如何实现队列的。

Java 中的队列有很多,例如:ArrayBlockingQueueLinkedBlockingQueuePriorityQueueDelayQueueSynchronousQueue 等,那它们的作用是什么?又是如何分类的呢?

其实 Java 中的这些队列可以从不同的维度进行分类,例如可以从阻塞和非阻塞进行分类,也可以从有界和无界进行分类,而本文将从队列的功能上进行分类,例如:优先队列、普通队列、双端队列、延迟队列等。


虽然本文的重点是从功能上对队列进行解读,但其它分类也是 Java 中的重要概念,所以我们先来了解一下它们。

阻塞队列和非阻塞队列

阻塞队列(Blocking Queue)提供了可阻塞的 puttake 方法,它们与可定时的 offerpoll 是等价的。如果队列满了 put 方法会被阻塞等到有空间可用再将元素插入;如果队列是空的,那么 take 方法也会阻塞,直到有元素可用。当队列永远不会被充满时,put 方法和 take 方法就永远不会阻塞。


我们可以从队列的名称中知道此队列是否为阻塞队列,阻塞队列中包含 BlockingQueue 关键字,比如以下这些:

  • ArrayBlockingQueue

  • LinkedBlockingQueue

  • PriorityBlockingQueue

  • .......

阻塞队列功能演示

接下来我们来演示一下当阻塞队列的容量满了之后会怎样,示例代码如下:

import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;public class BlockingTest {public static void main(String[] args) throws InterruptedException {// 创建一个长度为 5 的阻塞队列ArrayBlockingQueue q1 = new ArrayBlockingQueue(5);// 新创建一个线程执行入列new Thread(() -> {// 循环 10 次for (int i = 0; i < 10; i++) {try {q1.put(i);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(new Date() + " | ArrayBlockingQueue Size:" + q1.size());}System.out.println(new Date() + " | For End.");}).start();// 新创建一个线程执行出列new Thread(() -> {for (int i = 0; i < 5; i++) {try {// 休眠 1SThread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (!q1.isEmpty()) {try {q1.take(); // 出列} catch (InterruptedException e) {e.printStackTrace();}}}}).start();}
}

以上代码的执行结果如下:

Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:1

Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:2

Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:3

Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:4

Mon Oct 19 20:16:12 CST 2020 | ArrayBlockingQueue Size:5

Mon Oct 19 20:16:13 CST 2020 | ArrayBlockingQueue Size:5

Mon Oct 19 20:16:14 CST 2020 | ArrayBlockingQueue Size:5

Mon Oct 19 20:16:15 CST 2020 | ArrayBlockingQueue Size:5

Mon Oct 19 20:16:16 CST 2020 | ArrayBlockingQueue Size:5

Mon Oct 19 20:16:17 CST 2020 | ArrayBlockingQueue Size:5

Mon Oct 19 20:16:17 CST 2020 | For End.

从上述结果可以看出,当 ArrayBlockingQueue 队列满了之后就会进入阻塞,当过了 1 秒有元素从队列中移除之后,才会将新的元素入列。

非阻塞队列

非阻塞队列也就是普通队列,它的名字中不会包含 BlockingQueue 关键字,并且它不会包含 put 和 take 方法,当队列满之后如果还有新元素入列会直接返回错误,并不会阻塞的等待着添加元素,如下图所示:

非阻塞队列的典型代表是 ConcurrentLinkedQueue 和 PriorityQueue

有界队列和无界队列

有界队列:是指有固定大小的队列,比如设定了固定大小的 ArrayBlockingQueue,又或者大小为 0 的 SynchronousQueue

无界队列:指的是没有设置固定大小的队列,但其实如果没有设置固定大小也是有默认值的,只不过默认值是 Integer.MAX_VALUE,当然实际的使用中不会有这么大的容量(超过 Integer.MAX_VALUE),所以从使用者的角度来看相当于 “无界”的。


按功能分类

接下来就是本文的重点了,我们以功能来划分一下队列,它可以被分为:普通队列、优先队列、双端队列、延迟队列、其他队列等,接下来我们分别来看。

1.普通队列

普通队列(Queue)是指实现了先进先出的基本队列,例如 ArrayBlockingQueue 和 LinkedBlockingQueue,其中 ArrayBlockingQueue 是用数组实现的普通队列,如下图所示:

LinkedBlockingQueue 是使用链表实现的普通队列,如下图所示:


常用方法

普通队列中的常用方法有以下这些:

  • offer():添加元素,如果队列已满直接返回 false,队列未满则直接插入并返回 true;

  • poll():删除并返回队头元素,当队列为空返回 null;

  • add():添加元素,此方法是对 offer 方法的简单封装,如果队列已满,抛出 IllegalStateException 异常;

  • remove():直接删除队头元素;

  • put():添加元素,如果队列已经满,则会阻塞等待插入;

  • take():删除并返回队头元素,当队列为空,则会阻塞等待;

  • peek():查询队头元素,但不会进行删除;

  • element():对 peek 方法进行简单封装,如果队头元素存在则取出并不删除,如果不存在抛出 NoSuchElementException 异常。

注意:一般情况下 offer() 和 poll() 方法配合使用,put() 和 take() 阻塞方法配合使用,add() 和 remove() 方法会配合使用,程序中常用的是 offer() 和 poll() 方法,因此这两个方法比较友好,不会报错

接下来我们以 LinkedBlockingQueue 为例,演示一下普通队列的使用:

import java.util.concurrent.LinkedBlockingQueue;static class LinkedBlockingQueueTest {public static void main(String[] args) {LinkedBlockingQueue queue = new LinkedBlockingQueue();queue.offer("Hello");queue.offer("Java");queue.offer("中文社群");while (!queue.isEmpty()) {System.out.println(queue.poll());}}
}

以上代码的执行结果如下:

Hello

Java

中文社群

2.双端队列

双端队列(Deque)是指队列的头部和尾部都可以同时入队和出队的数据结构,如下图所示:

接下来我们来演示一下双端队列 LinkedBlockingDeque 的使用:

import java.util.concurrent.LinkedBlockingDeque;/*** 双端队列示例*/
static class LinkedBlockingDequeTest {public static void main(String[] args) {// 创建一个双端队列LinkedBlockingDeque deque = new LinkedBlockingDeque();deque.offer("offer"); // 插入首个元素deque.offerFirst("offerFirst"); // 队头插入元素deque.offerLast("offerLast"); // 队尾插入元素while (!deque.isEmpty()) {// 从头遍历打印System.out.println(deque.poll());}}
}

以上代码的执行结果如下:

offerFirst

offer

offerLast

3.优先队列

优先队列(PriorityQueue)是一种特殊的队列,它并不是先进先出的,而是优先级高的元素先出队。

优先队列是根据二叉堆实现的,二叉堆的数据结构如下图所示:

二叉堆分为两种类型:一种是最大堆一种是最小堆。以上展示的是最大堆,在最大堆中,任意一个父节点的值都大于等于它左右子节点的值。

因为优先队列是基于二叉堆实现的,因此它可以将优先级最好的元素先出队。

接下来我们来演示一下优先队列的使用:

import java.util.PriorityQueue;public class PriorityQueueTest {// 自定义的实体类static class Viper {private int id; // idprivate String name; // 名称private int level; // 等级public Viper(int id, String name, int level) {this.id = id;this.name = name;this.level = level;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getLevel() {return level;}public void setLevel(int level) {this.level = level;}}public static void main(String[] args) {PriorityQueue queue = new PriorityQueue(10, new Comparator<Viper>() {@Overridepublic int compare(Viper v1, Viper v2) {// 设置优先级规则(倒序,等级越高权限越大)return v2.getLevel() - v1.getLevel();}});// 构建实体类Viper v1 = new Viper(1, "Java", 1);Viper v2 = new Viper(2, "MySQL", 5);Viper v3 = new Viper(3, "Redis", 3);// 入列queue.offer(v1);queue.offer(v2);queue.offer(v3);while (!queue.isEmpty()) {// 遍历名称Viper item = (Viper) queue.poll();System.out.println("Name:" + item.getName() +" Level:" + item.getLevel());}}
}

以上代码的执行结果如下:

Name:MySQL Level:5

Name:Redis Level:3

Name:Java Level:1

从上述结果可以看出,优先队列的出队是不考虑入队顺序的,它始终遵循的是优先级高的元素先出队

4.延迟队列

延迟队列(DelayQueue)是基于优先队列 PriorityQueue 实现的,它可以看作是一种以时间为度量单位的优先的队列,当入队的元素到达指定的延迟时间之后方可出队。


我们来演示一下延迟队列的使用:

import lombok.Getter;
import lombok.Setter;
import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;public class CustomDelayQueue {// 延迟消息队列private static DelayQueue delayQueue = new DelayQueue();public static void main(String[] args) throws InterruptedException {producer(); // 调用生产者consumer(); // 调用消费者}// 生产者public static void producer() {// 添加消息delayQueue.put(new MyDelay(1000, "消息1"));delayQueue.put(new MyDelay(3000, "消息2"));}// 消费者public static void consumer() throws InterruptedException {System.out.println("开始执行时间:" +DateFormat.getDateTimeInstance().format(new Date()));while (!delayQueue.isEmpty()) {System.out.println(delayQueue.take());}System.out.println("结束执行时间:" +DateFormat.getDateTimeInstance().format(new Date()));}static class MyDelay implements Delayed {// 延迟截止时间(单位:毫秒)long delayTime = System.currentTimeMillis();// 借助 lombok 实现@Getter@Setterprivate String msg;/*** 初始化* @param delayTime 设置延迟执行时间* @param msg       执行的消息*/public MyDelay(long delayTime, String msg) {this.delayTime = (this.delayTime + delayTime);this.msg = msg;}// 获取剩余时间@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}// 队列里元素的排序依据@Overridepublic int compareTo(Delayed o) {if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {return 1;} else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {return -1;} else {return 0;}}@Overridepublic String toString() {return this.msg;}}
}

以上代码的执行结果如下:

开始执行时间:2020-10-20 20:17:28

消息1

消息2

结束执行时间:2020-10-20 20:17:31

从上述结束执行时间和开始执行时间可以看出,消息 1 和消息 2 都正常实现了延迟执行的功能。

5.其他队列

在 Java 的队列中有一个比较特殊的队列 SynchronousQueue,它的特别之处在于它内部没有容器,每次进行 put() 数据后(添加数据),必须等待另一个线程拿走数据后才可以再次添加数据,它的使用示例如下:

import java.util.concurrent.SynchronousQueue;public class SynchronousQueueTest {public static void main(String[] args) {SynchronousQueue queue = new SynchronousQueue();// 入队new Thread(() -> {for (int i = 0; i < 3; i++) {try {System.out.println(new Date() + ",元素入队");queue.put("Data " + i);} catch (InterruptedException e) {e.printStackTrace();}}}).start();// 出队new Thread(() -> {while (true) {try {Thread.sleep(1000);System.out.println(new Date() + ",元素出队:" + queue.take());} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
}

以上代码的执行结果如下:

Mon Oct 19 21:00:21 CST 2020,元素入队

Mon Oct 19 21:00:22 CST 2020,元素出队:Data 0

Mon Oct 19 21:00:22 CST 2020,元素入队

Mon Oct 19 21:00:23 CST 2020,元素出队:Data 1

Mon Oct 19 21:00:23 CST 2020,元素入队

Mon Oct 19 21:00:24 CST 2020,元素出队:Data 2

从上述结果可以看出,当有一个元素入队之后,只有等到另一个线程将元素出队之后,新的元素才能再次入队。

总结

本文讲了 Java 中的 5 种队列:普通队列、双端队列、优先队列、延迟队列、其他队列。其中普通队列的典型代表为 ArrayBlockingQueueLinkedBlockingQueue,双端队列的代表为 LinkedBlockingDeque,优先队列的代表为 PriorityQueue,延迟队列的代表为 DelayQueue,最后还讲了内部没有容器的其他队列 SynchronousQueue


往期推荐

一文详解「队列」,手撸队列的3种方法!


动图演示:手撸堆栈的两种实现方法!


JDK 竟然是这样实现栈的?


关注我,每天陪你进步一点点!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545747.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql中int、bigint、smallint 和 tinyint的区别与长度

各种整形&#xff0c;总结留作参考。bigint从 -2^63 (-9223372036854775808) 到 2^63-1 (9223372036854775807) 的整型数据&#xff08;所有数字&#xff09;。存储大小为 8 个字节。int从 -2^31 (-2,147,483,648) 到 2^31 – 1 (2,147,483,647) 的整型数据&#xff08;所有数字…

linux——回射服务器多并发(多进程)

多并发原理如图&#xff0c;多个客户端连接一个服务器&#xff0c;无论哪个客户端发送数据给服务器&#xff0c;服务器都能把数据准确的返回给这个客户端。 在socket编程中&#xff0c;socket这种文件描述符被默认设置为阻塞&#xff0c;故而read函数和accept函数时阻塞函数&a…

算法图解:如何用两个栈实现一个队列?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;本文已收录至 https://github.com/vipstone/algorithm 《算法图解》系列。队列和栈是计算机中两个非常重要的数据结构&#…

notepad++ 偶数行_C ++程序查找前N个偶数的立方和

notepad 偶数行The problem is we have a number N and we have to find sum of first N Even natural numbers. 问题是我们有一个数N &#xff0c;我们必须找到前N个偶数自然数之和。 Example: 例&#xff1a; Input:n 3Output:288 (2^3 4^36^3)A simple solution is give…

Auto activation triggers for Java(代码提示)功能扩展

1.打开Eclipse 后“window”→“Preferences”&#xff0c;选择“java”&#xff0c;展开&#xff0c;“Editor”&#xff0c;选择“Content Assist”。2.选择“Content Assist”&#xff0c;然后看到右边&#xff0c;右边的“Auto-Activation”下面的“Auto Activation trigge…

Linux——回射服务器多并发(多线程)

多线程与多进程的做法区别不大&#xff0c;思路一样&#xff0c;都是执行两个死循环&#xff0c;一个循环等待客户端连接&#xff0c;一个循环与客户端通信。多进程的方式请点此处 服务器 #include <sys/socket.h> #include <pthread.h> #include <unistd.h&g…

AIGC中的视觉生成文献整理

文章目录 文件夹文献总览图像生成技术视频生成技术Video Generation with Text ConditionVideo Generation with other ConditionsVideo Editing 生成模型在其他任务上的应用扩散模型在数据标记上的应用可控的图像生成技术 文件夹文献总览 AIGC 视觉生成文献整理├── 图像生…

「递归算法」看这一篇就够了|多图

前言递归是一种非常重要的算法思想&#xff0c;无论你是前端开发&#xff0c;还是后端开发&#xff0c;都需要掌握它。在日常工作中&#xff0c;统计文件夹大小&#xff0c;解析xml文件等等&#xff0c;都需要用到递归算法。它太基础太重要了&#xff0c;这也是为什么面试的时候…

数组tostring方法_数组toString()方法以及JavaScript中的示例

数组tostring方法JavaScript toString()方法 (JavaScript toString() method) toString() method is used to convert an array to the string. It is called with the array name and returns the string containing array elements with comma separated. toString()方法用于…

JAVA解析JSON数据

在网页中想后台传递多个数据时&#xff0c;有时数据还是多个动态列表&#xff0c;数据很复杂时&#xff0c;JavaScript程序员喜欢把他们作为json串进行处理&#xff0c;后台收到后需要对json字符串进行解析&#xff0c;幸好有JSON-lib&#xff0c;这个Java类包用于把bean,map和…

为什么把端口号改为80之后,访问的时候就可以不写端口号

一个关于tomcat的问题为什么把端口号改为80之后&#xff0c;访问的时候就可以不写端口号因为80端口是许多web服务器的默认端口&#xff0c;比如iis和apache&#xff0c;所有为了方便,浏览器在不知道请求端口的情况下默认会访问服务器的80端口百度知道这样的话就可以恶作剧了打开…

linux——服务器与客户端实现聊天功能

先联想一下聊天的场景&#xff0c;假设甲和乙在聊天&#xff0c;他们每个人都能够发送给对方一句话甚至多句话&#xff0c;也能接收到对方发来的一句或多句话&#xff0c;也就是说&#xff0c;甲在发送一句话给乙的时候&#xff0c;同时也能接收到乙发来的信息&#xff0c;而且…

有关链表的小技巧,我都给你总结好了

链表链表是数据结构里一个很基础但是又很爱考的线性结构&#xff0c;链表的操作相对来说比较简单&#xff0c;但是非常适合考察面试者写代码的能力&#xff0c;以及对 corner case 的处理&#xff0c;还有指针的应用很容易引起 NPE (null pointer exception)。综合以上原因&…

long类型20位示例_Java Long类numberOfTrailingZeros()方法及示例

long类型20位示例长类numberOfTrailingZeros()方法 (Long class numberOfTrailingZeros() method) numberOfTrailingZeros() method is available in java.lang package. 在java.lang包中提供了numberOfTrailingZeros()方法 。 numberOfTrailingZeros() method is used to retu…

ActiveReports 9实战教程(1): 手把手搭建环境Visual Studio 2013 社区版

ActiveReports 9刚刚发布3天&#xff0c;微软就发布了 Visual Studio Community 2013 开发环境。Visual Studio Community 2013 提供完整功能的 IDE &#xff0c;可开发 Windows、Android 和 iOS 应用。支持&#xff1a;C, Python, HTML5, JavaScript, 和 C#,VB, F# 语言的开发…

第 1-1 课:Java 程序是如何执行的?

了解任何一门语言的精髓都是先俯览其全貌&#xff0c;从宏观的视角把握全局&#xff0c;然后再深入每个知识点逐个击破&#xff0c;这样就可以深入而快速的掌握一项技能。同样学习 Java 也是如此&#xff0c;本节就让我们先从整体来看一下 Java 中的精髓。 Java 介绍 Java 诞…

linux——两个客户端之间实现聊天(TCP、单线程)

两个客户端实现聊天功能&#xff0c;那么服务器作转发信息的作用&#xff0c;客户端A先将信息发送到服务器&#xff0c;在由服务器将信息发送到客户端B&#xff0c;客户端B也是一样。客户端与服务器都应该有两个执行流&#xff0c;服务器的一个执行流不断的接收客户端A的信息并…

Java ClassLoader getSystemClassLoader()方法与示例

ClassLoader类getSystemClassLoader()方法 (ClassLoader Class getSystemClassLoader() method) getSystemClassLoader() method is available in java.lang package. getSystemClassLoader()方法在java.lang包中可用。 getSystemClassLoader() method is used to find the Sys…

zabbix邮件通知,短信通知配置详解

一、使用邮件发送报警1、前提条件是zabbix我们已经安装完成2、在官网我们下载msmtp的文件http://sourceforge.net/projects/msmtp/files/msmtp/1.4.32/msmtp-1.4.32.tar.bz2/download tar xf msmtp-1.4.32.tar.bz2 cd msmtp-1.4.32 ./configure--prefix/usr/local/msmtp make m…

关于怎么获取jsp的web站点的目录问题

发布了自己的web站点之后&#xff0c;想要访问站点文件夹下的某个文本文件&#xff0c;但是却不知道怎么找到文件根目录&#xff0c;一直尝试总是找不到文件……好不容易在网上翻了堆代码找到两句我现在急用的……so//根目录路径的获取System.out.println(request.getSession()…