作者 | 王磊

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

通过前面文章的学习《一文详解「队列」,手撸队列的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

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

Java中的5大队列,你知道几个?相关推荐

  1. java add offer_图解Java中的5大队列!(干货收藏)

    Java 中的队列有很多,例如:ArrayBlockingQueue.LinkedBlockingQueue.PriorityQueue.DelayQueue.SynchronousQueue等,那它 ...

  2. Java中的堆栈和队列

    我最近一直在研究一些需要堆栈和队列的Java代码. 使用的选择不是立即显而易见的. 有一个Queue接口,但没有明确的具体实现要使用. 还有一个Stack类,但是javadocs指出其他类" ...

  3. JAVA中如何将大数字或字符串放进数组

    JAVA中如何将大数字或字符串放进数组 在JAVA学习过程中字符串.数组.数字之间的 的转换经常用到,记录一些非常基础的转换. 1.将数字转换为字符串:String str = String.valu ...

  4. java中的数组、队列、堆栈

    声明一个数组 Java代码 String[] aArray = new String[5]; String[] bArray = {"a","b"," ...

  5. Java中的PriorityQueue优先级队列

    以前的博客中介绍过队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,此时出队列时需要优先级高的元素先出队列,这个时候传统的队列显然不能胜任,Java中有一个新的实现类继 ...

  6. Java中的十大组织

    十大组织 Sun : 因为Java而永被荣光 文/孟岩 Sun是1980年代初期由斯坦福大学三位年轻学生创立的公司.与一般人的印象不同,"SUN"的本意并不是企图剽窃天上那颗温暖的 ...

  7. 我希望我们在Java中拥有十大锡兰语言功能

    当Hibernate"完成"并且功能完善并且需要新的挑战时,该怎么办? 对. 人们创建了一种新的JVM语言,称为Ceylon . 2013年11月12日, Ceylon 1.0.0 ...

  8. java中格式化输出大神Formatter简历

    一.干嘛的?(作用) 在java中,所有新的格式化功能都有formatter类处理.可以将formatter看作一个翻译器,它将你的格式化字符串与数据翻译成需要的结果.比如很多地方格式输出都有用到,像 ...

  9. Java 中的栈和队列

    目录 ​1. 栈 ​1.1 概念 1.2 栈的种类 1.3 栈的实现 2. 队列 2.1 概念 ​2.2 队列实现 3. 循环队列 3.1 为什么会有循环队列 3.2 如何区分空与满 3.3 循环队列 ...

最新文章

  1. linux redhat 下载_使用Vmware创建Linux(Ubuntu)系统
  2. maven 与intellij IDEA 下maven 为groovy 项目生成jar 详解
  3. 如何使用“Hash文件信息校验” 工具
  4. delphi virtual dynamic
  5. 【sping揭秘】19、关于spring中jdbctemplate中的DataSource怎么来呢
  6. linux localhost的修改
  7. 学习nodejs+express+angular+socket.io ,投票demo
  8. 视觉在无人驾驶中的应用及分类_紫外光在机器视觉中的应用
  9. Python多线程学习资料1
  10. 多变量微积分笔记5——梯度与方向导数
  11. 二维码编辑网站推荐,哪个更好用?
  12. 在线教育,网校平台,线上平台,线上教育的一些个人理解!
  13. 经常使用的网页开发工具有哪些
  14. Python批量采集某网站高清壁纸,这下不用担心没壁纸换了
  15. 如何解决“504 Gateway Time-out”错误
  16. NSGA-2学习笔记
  17. ubuntu 20.04 安装 gedit
  18. MGRE结合OSPF
  19. 09年生日送我的煎蛋超级技术
  20. 企企通携手「帝迈生物」打造的数字化采购平台成功上线,客户发来暖心感谢信

热门文章

  1. 学习一个 Linux 命令:shutdown 命令
  2. 不学51直接学stm32可以吗?学stm32需要哪些基础?
  3. 关于学习Python的一点学习总结(27->关键字参数和默认值)
  4. 索引超出了数组界限_还在用优先队列?来试试索引优先队列吧(优先队列amp;索引优先队列)...
  5. 0x22.搜索 - 深度优先搜索
  6. 牛客练习赛61 E 相似的子串(二分+哈希)难度⭐⭐⭐
  7. 买个云服务器有啥用_如何用阿里云轻量应用服务器配置一个WordPress网站?
  8. 场景法:基本流、备选流、构造场景
  9. php statements,PHP PDOStatement::setAttribute讲解
  10. 不能成为专业软件测试人员的10大理由