JAVA并发(6)-并发队列ArrayBlockingQueue
本文讲ArrayBlockingQueue
1. 介绍
一个基于数组的有界阻塞队列,FIFO顺序。支持等待消费者和生产者线程的可选公平策略(默认是非公平的)。公平的话通常会降低吞吐量,但是可以减少可变性并避免之前被阻塞的线程饥饿。
1.1 类结构
- ArrayBlockingQueue继承关系
- ArrayBlockingQueue类图
构造器
// 默认是非公平的
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
...
}
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
...
}
比较重要的几个参数
// 储存元素的数组
final Object[] items;
/** items index for next take, poll, peek or remove */
// 与putIndex相互配合可以将数组变成一个可循环利用的数组,不需要扩容,后面会讲到
// 每次出队的索引
int takeIndex;
/** items index for next put, offer, or add */
// 每次入队的索引
int putIndex;
/** Number of elements in the queue */
int count;
/**
* Shared state for currently active iterators, or null if there
* are known not to be any. Allows queue operations to update
* iterator state.
*/
// 迭代的时候会用到,在后面详讲
transient Itrs itrs = null;
保证线程安全的措施
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
我们可以看到ArrayBlockingQueue使用的是单锁控制线程安全,而LinkedBlockingQueue是双锁控制的, 后者的细粒度更小。
2. 源码剖析
ArrayBlockingQueue也是继承至BlockingQueue(可以去看看上面提到的那篇博客有提到BlockingQueue),它对于不同的方法不能立即满足要求的,作出的回应是不一样的。
我们分别介绍下面的方法的具体实现
- offer(E e)
- offer(E e, long timeout, TimeUnit unit)
- put(E e)
- poll()
- remove(Object o)
2.1 offer(E e) & poll()
插入成功就返回true;若队列满了就直接返回false,不会阻塞自己
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
上面的代码比较简单,我们来看看入队的具体操作
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
// 为什么putIndex+1 等于数组长度时会变成0
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
为了解答上面注释中的问题,我们先看看poll()的实现
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
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;
// takeIndex + 1等于了数组的长度也会将值置为0
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
结合上面的入队、出队源码,我们来分析一下:
- 单线程下,首先执行
ArrayBlockingQueue<String> array = new ArrayBlockingQueue<>(3);
array.offer("A");
array.offer("B");
array.offer("C");
此时队列的状态
- 再执行
array.poll();
array.offer("D");
最后队列的状态
大家可能会有点疑问,上面的队列不是输出是"D B C", 咋回事? 肯定不是啦,我们看看类重写的toString就明白了。
public String toString() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k == 0)
return "[]";
final Object[] items = this.items;
StringBuilder sb = new StringBuilder();
sb.append('[');
// 主要代码
for (int i = takeIndex; ; ) {
Object e = items[i];
sb.append(e == this ? "(this Collection)" : e);
if (--k == 0)
return sb.append(']').toString();
sb.append(',').append(' ');
if (++i == items.length)
i = 0;
}
} finally {
lock.unlock();
}
}
思考一下,就会明白了。
通过上面的分析,我们看出了数组就像一个循环数组一样,每个地址都被重复使用。我们也知道了基于数组的队列如何实现的。
offer(E e, long timeout, TimeUnit unit) 与 put(E e)实现都比较简单,大家看看源码即可。
2.2 remove(Object o)
若o存在则移除,返回true;反之。这个操作会改变队列的结构,但是该方法一般很少使用
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i])) {
// 主要删除逻辑
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
void removeAt(final int removeIndex) {
// assert lock.getHoldCount() == 1;
// assert items[removeIndex] != null;
// assert removeIndex >= 0 && removeIndex < items.length;
final Object[] items = this.items;
if (removeIndex == takeIndex) {
// removing front item; just advance
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// an "interior" remove
// slide over all others up through putIndex.
// 此时removeIndex != takeIndex
// 为啥要执行下面的代码,大家可以按照上面图片的最后状态,
// 按照下面代码走一下,就明白了.主要是设置putIndex
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
2.3 解释解释Itrs
// 当前活动迭代器的共享状态; 允许队列操作更新迭代器的状态;
transient Itrs itrs = null;
这个变量可以理解成,在一个线程使用迭代器时,其他的线程可以对队列进行更新操作的一个保障。
源码注释中对Itrs的描述,迭代器和它们的队列之间共享数据,允许在删除元素时修改队列以更新迭代器。 我们可以看到对队列进行了删除操作时,队列都会执行下面的语句
if (itrs != null)
itrs.removedAt(removeIndex);
初始化该值是在使用迭代器时
public Iterator<E> iterator() {
return new Itr();
}
...
Itr() {
// assert lock.getHoldCount() == 0;
lastRet = NONE;
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
...
itrs = new Itrs(this);
...
}
} finally {
lock.unlock();
}
}
3. 总结
ArrayBlockingQueue的实现整体不难,使用ReetrantLock保证了线程安全,putIndex与takeIndex分别维护入队与出队的位置,一起构成一个循环数组
JAVA并发(6)-并发队列ArrayBlockingQueue的更多相关文章
- Java中的阻塞队列-ArrayBlockingQueue(一)
最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...
- java并发:阻塞队列
第一节 阻塞队列 1.1 初识阻塞队列 队列以一种先进先出的方式管理数据,阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:在队列为空时,获取元素的线程会等待队列 ...
- 聊聊并发(七)——Java中的阻塞队列
3. 阻塞队列的实现原理 聊聊并发(七)--Java中的阻塞队列 作者 方腾飞 发布于 2013年12月18日 | ArchSummit全球架构师峰会(北京站)2016年12月02-03日举办,了解更 ...
- Java并发编程-阻塞队列(BlockingQueue)的实现原理
背景:总结JUC下面的阻塞队列的实现,很方便写生产者消费者模式. 常用操作方法 常用的实现类 ArrayBlockingQueue DelayQueue LinkedBlockingQueue Pri ...
- Java编程的逻辑 (76) - 并发容器 - 各种队列
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- Java并发编程——阻塞队列BlockingQueue
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
- 【Java并发】并发队列与线程池
并发队列 阻塞队列与非阻塞队 ConcurrentLinkedQueue BlockingQueue ArrayBlockingQueue LinkedBlockingQueue PriorityBl ...
- Java并发编程笔记之ArrayBlockingQueue源码分析
JDK 中基于数组的阻塞队列 ArrayBlockingQueue 原理剖析,ArrayBlockingQueue 内部如何基于一把独占锁以及对应的两个条件变量实现出入队操作的线程安全? 首先我们先大 ...
- [Java并发] AQS抽象队列同步器源码解析--锁获取过程
要深入了解java并发知识,AbstractQueuedSynchronizer(AQS)是必须要拿出来深入学习的,AQS可以说是贯穿了整个JUC并发包,例如ReentrantLock,CountDo ...
- [Java并发] AQS抽象队列同步器源码解析--独占锁释放过程
[Java并发] AQS抽象队列同步器源码解析--独占锁获取过程 上一篇已经讲解了AQS独占锁的获取过程,接下来就是对AQS独占锁的释放过程进行详细的分析说明,废话不多说,直接进入正文... 锁释放入 ...
随机推荐
- SSH后门万能密码
当我们在获得一台Linux服务器的 root 权限后,我们第一想做的就是如何维持这个权限,维持权限肯定想到的就是在目标服务器留下一个后门.但是留普通后门,肯定很容易被发现.我们今天要讲的就是留一个SS ...
- Linux-鸟菜-1-Linux简介
Linux-鸟菜-1-Linux简介 鸟哥这一章是介绍了Linux的由来以及建议怎么学Linux.很多也是建议性的东西,概念性的东西(当然我不是排斥概念).把最后鸟哥的汇总粘过来吧. 建议大家去看下这 ...
- 【JavaScript】【dp】Leetcode每日一题-解码方法
[JavaScript]Leetcode每日一题-解码方法 [题目描述] 一条包含字母 A-Z 的消息通过以下映射进行了 编码 : 'A' -> 1 'B' -> 2 ... 'Z' -& ...
- 进程Queue和线程Queue区别
进程Queue from multiprocessing import Queue q=Queue() 线程Queue import queue q=queue.Queue()
- 【转载】CentOS下查看电脑硬件设备属性命令
CentOS下查看电脑硬件设备属性命令2018年09月13日 17:48:31 乔烨 阅读数 510如何在linux下查看电脑硬件设备属性 # uname -a # 查看内核/操作系统/CPU信息 # ...
- window 共享打印机
https://www.zhihu.com/question/20653708 https://h30471.www3.hp.com/t5/da-yin-ji-yu-sao-miao-yi-de-an ...
- 解决Windows路径太长的目录以及文件名超长删除的问题
因Windows文件夹有长度限制,在路径太深,长度达到600多个字符时,删除文件时出现报错"源文件名长度大于文件系统支持的长度.请尝试将其移动到具有较短路径名称的位置,或者在执行此操作前尝试 ...
- C++ STL 里为什么不维护一个 size 成员变量?
回答: 为什么 GCC 里要把 list::size() 的复杂度搞成 O(N)? 一通搜索后终于看到有这样的讨论:关于 list::splice() 函数. list 是链表结构,它的优势就在于可以 ...
- 浅谈:@RequestBody,@ResponseBody 和 @RequestParam
1. @RequestBody @RequestBody:主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据) GET方式无请求体,所以使用@RequestBody接收数据时,前端不 ...
- C语言编程 菜鸟练习100题(51-60)
[练习51]矩阵转置 0. 题目: 矩阵的转置 1. 分析: 练习使用 for 循环嵌套,多维数组的表达. 2. 程序: #include <stdio.h> int main() { i ...