文章目录

  • 一、分类
  • 二、BlockingQueue 阻塞队列
  • 三、ConcurrentLinkedQueue 非阻塞队列

一、分类

java中所有队列都继承至java.util.Queue接口,该接口定义了以下三组方法:

方法名 抛出异常 返回特殊值
插入 add(e) offer(e)
移除 remove() poll()
检查 element() peek()

Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现, 其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue。

二、BlockingQueue 阻塞队列

BlockingQueue 对插入操作、移除操作、获取元素操作提供了四种不同的方法用于不同的场景中使用:

  • 1、抛出异常;
  • 2、返回特殊值(null 或 true/false,取决于具体的操作);
  • 3、阻塞等待此操作,直到这个操作成功;
  • 4、阻塞等待此操作,直到成功或者超时指定时间。总结如下:
抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
检查 element() peek() 不可用 不可用

从上表可以很明显看出每个方法的作用,这个不用多说。我想说的是:

  • add(e) remove() element()方法不会阻塞线程。当不满足约束条件时,会抛出IllegalStateException 异常。例如:当队列被元素填满后,再调用add(e),则会抛出异常。
  • offer(e) poll() peek() 方法即不会阻塞线程,也不会抛出异常。例如:当队列被元素填满后,再调用offer(e),则不会插入元素,函数返回false。
  • 要想要实现阻塞功能,需要调用put(e) take()方法。 当不满足约束条件时,会阻塞线程。其实质可以用一个锁(入队和出队共享一把锁)来实现线程安全。以ArrayBlockQueue源码中put(e)/take()源码如下:
/*** Inserts the specified element at the tail of this queue, waiting* for space to become available if the queue is full.** @throws InterruptedException {@inheritDoc}* @throws NullPointerException {@inheritDoc}*/public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length)notFull.await();enqueue(e);} finally {lock.unlock();}}/*** Inserts element at current put position, advances, and signals.* Call only when holding lock.*/private void enqueue(E x) {// assert lock.getHoldCount() == 1;// assert items[putIndex] == null;final Object[] items = this.items;items[putIndex] = x;if (++putIndex == items.length)putIndex = 0;count++;notEmpty.signal();}public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}}/*** Extracts element at current take position, advances, and signals.* Call only when holding lock.*/private E dequeue() {// assert lock.getHoldCount() == 1;// assert items[takeIndex] != null;final Object[] items = this.items;@SuppressWarnings("unchecked")E x = (E) items[takeIndex];items[takeIndex] = null;if (++takeIndex == items.length)takeIndex = 0;count--;if (itrs != null)itrs.elementDequeued();notFull.signal();return x;}

BlockingQueue是个接口,有如下常用实现类:

  • ArrayBlockQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象必须明确大小,像数组一样。
  • LinkedBlockQueue:一个可改变大小的阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象如果没有明确大小,默认值是Integer.MAX_VALUE。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
  • PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。
  • SynchronousQueue:同步队列。同步队列没有任何容量,每个插入必须等待另一个线程移除,反之亦然。

使用示例:

ArrayBlockQueue使用(生产者-消费者):https://www.jianshu.com/p/b1408e3e3bb4

三、ConcurrentLinkedQueue 非阻塞队列

ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。基于CAS的“wait-free”(常规无等待)来实现,CAS并不是一个算法,它是一个CPU直接支持的硬件指令,这也就在一定程度上决定了它的平台相关性。

再通过源码来详细分析下它是如何使用循环CAS的方式来入队的(JDK1.8)

public boolean offer(E e) {checkNotNull(e);//创建入队节点final Node<E> newNode = new Node<E>(e);//t为tail节点,p为尾节点,默认相等,采用失败即重试的方式,直到入队成功for (Node<E> t = tail, p = t;;) {//获得p的下一个节点Node<E> q = p.next;// 如果下一个节点是null,也就是p节点就是尾节点if (q == null) {//将入队节点newNode设置为当前队列尾节点p的next节点if (p.casNext(null, newNode)) { //判断tail节点是不是尾节点,也可以理解为如果插入结点后tail节点和p节点距离达到两个结点if (p != t) //如果tail不是尾节点则将入队节点设置为tail。// 如果失败了,那么说明有其他线程已经把tail移动过 casTail(t, newNode);  return true;}}// 如果p节点等于p的next节点,则说明p节点和q节点都为空,表示队列刚初始化,所以返回 head节点else if (p == q)p = (t != (t = tail)) ? t : head;else//p有next节点,表示p的next节点是尾节点,则需要重新更新p后将它指向next节点p = (p != t && t != (t = tail)) ? t : q;}}public E poll() {// 设置起始点  restartFromHead:for (;;) {//p表示head结点,需要出队的节点for (Node<E> h = head, p = h, q;;) {//获取p节点的元素E item = p.item;//如果p节点的元素不为空,使用CAS设置p节点引用的元素为nullif (item != null && p.casItem(item, null)) {if (p != h) // hop two nodes at a time//如果p节点不是head节点则更新head节点,也可以理解为删除该结点后检查head是否与头结点相差两个结点,如果是则更新head节点updateHead(h, ((q = p.next) != null) ? q : p);return item;}//如果p节点的下一个节点为null,则说明这个队列为空,更新head结点else if ((q = p.next) == null) {updateHead(h, p);return null;}//结点出队失败,重新跳到restartFromHead来进行出队else if (p == q)continue restartFromHead;elsep = q;}}}

ConcurrentLinkedQueue 的非阻塞算法实现主要可概括为下面几点:

  • 使用 CAS 原子指令来处理对数据的并发访问,这是非阻塞算法得以实现的基础。
  • head/tail 并非总是指向队列的头 / 尾节点,也就是说允许队列处于不一致状态。 这个特性把入队/出队时,原本需要一起原子化执行的两个步骤分离开来,从而缩小了入队 /出队时需要原子化更新值的范围到唯一变量。这是非阻塞算法得以实现的关键。
  • 以批处理方式来更新head/tail,从整体上减少入队 / 出队操作的开销。

使用示例:

Java ConcurrentLinkedQueue队列线程安全操作:https://yq.aliyun.com/articles/615890/

参考:

解读 Java 并发队列 BlockingQueue:https://javadoop.com/post/java-concurrent-queue

Java多线程高并发学习笔记——阻塞队列:https://cloud.tencent.com/developer/article/1090012

Java线程安全队列:https://www.jianshu.com/p/ad6ef76e067a

第二十一章、java线程安全队列:https://www.jianshu.com/p/04aeb0088dec

java并发之ConcurrentLinkedQueue:https://www.jianshu.com/p/24516e7853d1

ConcurrentLinkedQueue的实现原理和源码分析:https://www.jianshu.com/p/26d9745614dd

Java线程(十):CAS:https://www.kancloud.cn/digest/java-thread/107465

一文弄懂Java线程安全队列相关推荐

  1. deque stack java_一文弄懂java中的Queue家族

    简介 java中Collection集合有三大家族List,Set和Queue.当然Map也算是一种集合类,但Map并不继承Collection接口. List,Set在我们的工作中会经常使用,通常用 ...

  2. 一文搞懂 Java 线程中断

    转载自   一文搞懂 Java 线程中断 在之前的一文<如何"优雅"地终止一个线程>中详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程 ...

  3. 一张图弄懂java线程的状态和生命周期

    转载自 一张图弄懂java线程的状态和生命周期 上图是一个线程的生命周期状态流转图,很清楚的描绘了一个线程从创建到终止的过程. 这些状态的枚举值都定义在java.lang.Thread.State下 ...

  4. 一文弄懂Java中线程池原理

    在工作中,我们经常使用线程池,但是你真的了解线程池的原理吗?同时,线程池工作原理和底层实现原理也是面试经常问的考题,所以,今天我们一起聊聊线程池的原理吧. 为什么要用线程池 使用线程池主要有以下三个原 ...

  5. java吵醒线程_一文搞懂 Java 线程中断

    在之前的一文<如何"优雅"地终止一个线程>中详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程的方法吗?答案是肯定的,它就是我们今天要分 ...

  6. java sleep方法_一文搞懂 Java 线程中断!

    在之前的一文<如何"优雅"地终止一个线程>详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程的方法吗?答案是肯定的,它就是我们今天要分享 ...

  7. 一文弄懂java中的Queue家族

    文章目录 简介 Queue接口 Queue的分类 BlockingQueue Deque TransferQueue 总结 java中Queue家族简介 简介 java中Collection集合有三大 ...

  8. queue double java_一文弄懂java中的Queue家族

    java中Queue家族简介 简介 java中Collection集合有三大家族List,Set和Queue.当然Map也算是一种集合类,但Map并不继承Collection接口. List,Set在 ...

  9. 一文弄懂Java设计模式之建造者模式:图解+游戏角色生成实例

    文章目录 详解Java设计模式之建造者模式 案例引入建造者模式 建造者模式 定义 UML类图表示 模式结构 案例分析与代码实现 案例类图实现 代码结构 代码实现 结果分析 指挥者类的深入讨论 钩子方法 ...

最新文章

  1. android运用 sqlite 实现简单的通讯录_大一新生作品:利用 C 语言实现quot;通讯录管理系统quot;,直言太简单...
  2. 数字图像处理中所用数学工具3---算术操作处理图像
  3. JavaScript的预编译及执行顺序
  4. 从零入门 Serverless | SAE 场景下,应用流量的负载均衡及路由策略配置实践
  5. 做一个有梦想的咸鱼!
  6. linux克隆的虚拟,linux(CentOS7)下克隆虚拟机并配置网络(固定ip)
  7. ubuntu没有声音-只有类比立体声输入
  8. [有奖励]GeneralUpdate开源项目招募开发者
  9. pip 更改源   pip加速
  10. 一键洞察全量SQL ,远离性能异常
  11. scala类的序列化_Scala序列理解,通用类和内部类示例
  12. ZStack GPU解决方案
  13. 4linux--------操作系统介绍 - 项目前导必备------全栈式开发40
  14. Docker参数 -i -t 的作用
  15. 下载erlang的.rpm文件 erlang下载 centos安装rabbitmq
  16. Python爬虫实例:爬取猫眼电影——破解字体反爬
  17. VC++钩子使用之全局键盘钩子
  18. 【优化求解】粒子群优化和重力搜索算法求解MLP问题matlab代码
  19. 数字时代,如何跟上互联网医院的建设潮?
  20. 折腾两年裁了150人,谷歌才明白做游戏有多费钱

热门文章

  1. go语言android开发环境搭建,golang开发android应用(一) - go语言android应用环境搭建...
  2. 华为p50预计售价鸿蒙是什么,华为P50没赶上首发鸿蒙系统,还有哪些值得期待的地方?...
  3. 使用prometheus(普罗米修斯)监控mysql容器详解
  4. 人体五脏简单有效的排毒方法
  5. python图片转文本(字符画)
  6. Java Zip解压缩文件夹工具类 ----ZipUtils
  7. 苹果iPhone 13系列有望支持息屏显示
  8. git切换分支冲突的解决
  9. 风铃系统中,微信公众号服务平台如何搭建?
  10. COLING 2020 | 面向医疗对话的摘要生成