一文弄懂Java线程安全队列
文章目录
- 一、分类
- 二、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线程安全队列相关推荐
- deque stack java_一文弄懂java中的Queue家族
简介 java中Collection集合有三大家族List,Set和Queue.当然Map也算是一种集合类,但Map并不继承Collection接口. List,Set在我们的工作中会经常使用,通常用 ...
- 一文搞懂 Java 线程中断
转载自 一文搞懂 Java 线程中断 在之前的一文<如何"优雅"地终止一个线程>中详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程 ...
- 一张图弄懂java线程的状态和生命周期
转载自 一张图弄懂java线程的状态和生命周期 上图是一个线程的生命周期状态流转图,很清楚的描绘了一个线程从创建到终止的过程. 这些状态的枚举值都定义在java.lang.Thread.State下 ...
- 一文弄懂Java中线程池原理
在工作中,我们经常使用线程池,但是你真的了解线程池的原理吗?同时,线程池工作原理和底层实现原理也是面试经常问的考题,所以,今天我们一起聊聊线程池的原理吧. 为什么要用线程池 使用线程池主要有以下三个原 ...
- java吵醒线程_一文搞懂 Java 线程中断
在之前的一文<如何"优雅"地终止一个线程>中详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程的方法吗?答案是肯定的,它就是我们今天要分 ...
- java sleep方法_一文搞懂 Java 线程中断!
在之前的一文<如何"优雅"地终止一个线程>详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程的方法吗?答案是肯定的,它就是我们今天要分享 ...
- 一文弄懂java中的Queue家族
文章目录 简介 Queue接口 Queue的分类 BlockingQueue Deque TransferQueue 总结 java中Queue家族简介 简介 java中Collection集合有三大 ...
- queue double java_一文弄懂java中的Queue家族
java中Queue家族简介 简介 java中Collection集合有三大家族List,Set和Queue.当然Map也算是一种集合类,但Map并不继承Collection接口. List,Set在 ...
- 一文弄懂Java设计模式之建造者模式:图解+游戏角色生成实例
文章目录 详解Java设计模式之建造者模式 案例引入建造者模式 建造者模式 定义 UML类图表示 模式结构 案例分析与代码实现 案例类图实现 代码结构 代码实现 结果分析 指挥者类的深入讨论 钩子方法 ...
最新文章
- android运用 sqlite 实现简单的通讯录_大一新生作品:利用 C 语言实现quot;通讯录管理系统quot;,直言太简单...
- 数字图像处理中所用数学工具3---算术操作处理图像
- JavaScript的预编译及执行顺序
- 从零入门 Serverless | SAE 场景下,应用流量的负载均衡及路由策略配置实践
- 做一个有梦想的咸鱼!
- linux克隆的虚拟,linux(CentOS7)下克隆虚拟机并配置网络(固定ip)
- ubuntu没有声音-只有类比立体声输入
- [有奖励]GeneralUpdate开源项目招募开发者
- pip 更改源 pip加速
- 一键洞察全量SQL ,远离性能异常
- scala类的序列化_Scala序列理解,通用类和内部类示例
- ZStack GPU解决方案
- 4linux--------操作系统介绍 - 项目前导必备------全栈式开发40
- Docker参数 -i -t 的作用
- 下载erlang的.rpm文件 erlang下载 centos安装rabbitmq
- Python爬虫实例:爬取猫眼电影——破解字体反爬
- VC++钩子使用之全局键盘钩子
- 【优化求解】粒子群优化和重力搜索算法求解MLP问题matlab代码
- 数字时代,如何跟上互联网医院的建设潮?
- 折腾两年裁了150人,谷歌才明白做游戏有多费钱
热门文章
- go语言android开发环境搭建,golang开发android应用(一) - go语言android应用环境搭建...
- 华为p50预计售价鸿蒙是什么,华为P50没赶上首发鸿蒙系统,还有哪些值得期待的地方?...
- 使用prometheus(普罗米修斯)监控mysql容器详解
- 人体五脏简单有效的排毒方法
- python图片转文本(字符画)
- Java Zip解压缩文件夹工具类 ----ZipUtils
- 苹果iPhone 13系列有望支持息屏显示
- git切换分支冲突的解决
- 风铃系统中,微信公众号服务平台如何搭建?
- COLING 2020 | 面向医疗对话的摘要生成