java源码阅读ArrayBlockingQueue
1类签名与简介
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
一个有限的阻塞队列由数组支持。 这个队列排列元素FIFO(先进先出)。新元素插入队列的尾部,队列检索操作获取队列头部的元素。
ArrayBlockingQueue是线程安全的,其内部通过“互斥锁”(Lock)保护竞争资源,实现了多线程对竞争资源的互斥访问。
ArrayBlockingQueue的容量是固定的,确定之后无法更改。该实现既包含了一组非阻塞的操作的API,也包括了一组阻塞操作的API。
阻塞情况有如下两种:(1)当队列为null时,取操作会阻塞;(2)当队列为满时,插入操作会被阻塞。
ArrayBlockingQueue是并发包中的类,可以用来实现经典的“生产者/消费者”模型。
2重要属性
//items数组存储队列中的元素
final Object[] items; //下一个取元素的索引(take, poll, peek or remove )
int takeIndex; //下一个插入元素应该的索引(put, offer, or add)
int putIndex; //队列中元素的个数
int count; /** Main lock guarding all access */
final ReentrantLock lock; /** Condition for waiting takes */
private final Condition notEmpty; /** Condition for waiting puts */
private final Condition notFull;
takeIndex表示下一个待取元素的索引,对于队列来说就是队头,相应的putIndex指向队尾。lock是ArrayBlockingQueue类的主锁,该类的主要方法都要加上此锁。
notEmpty和notFull实现了队列的阻塞,当为队列为null时,再取元素就会调用notEmpty.await()进入阻塞状态,当入队时肯定不为空,就会调用notEmpty.signal()唤醒阻塞的线程继续执行。同理notFull就不多介绍,对Lock锁与Condition 不熟悉的多线程基本知识了解一下。
3 基本操作(入队/出队)
(1)入队enqueue
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();
}
首先,在队尾也就是putIndex指向的位置插入新元素;然后,putIndex增1,若此时putIndex等于items.length(超过了数组的最后一位items.length-1),putIndex循环到数组的第一位。同理面的takeIndex也有次操作,所以该队列是由循环数组实现的,每次插入队尾后移1位,每次取出队头向后移1位。图就不画,自行脑补。然后计数count++;最后随机唤醒1个由notEmpty.await阻塞的线程(若有的话),因为有元素入队了,此时队列肯定不为null了。
(2)出队dequeue
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;
}
删除队头元素。
4常用方法
(1)添加/入队(add、offer、put)
add方法如下
public boolean add(E e) {
return super.add(e);
}
//父类AbstractQueue中的add
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
ArrayBlockingQueue的add调用了父类AbstractQueue的add,add方法调用offer方法进行插入,offer返回true则add成功,offer返回false也就是插入失败会抛出异常。ArrayBlockingQueue实现了自己的offer,所以此处虽然调用父类的add,但是继承过来的add会调用自己的offer(多态)。
offer代码如下
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();
}
}
这里offer调用enqueue进行入队操作,当队列为满时不会阻塞,而是直接返回false。所以与add相比,offer一个明显的不同是,插入失败时不会报异常。但是add与这里的offer都不是阻塞方法。offer也提供了一个阻塞的重载版本
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException { checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
队列若满,则线程会阻塞timeout长的时间,超时唤醒后会继续判断队列是否为满。还有一个添加的阻塞方法就是put,对于阻塞似乎put方法更加常用。
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();
}
}
与上面的offer类似,不同的是没有阻塞时间,需要被notFull Condition来唤醒。
(2)删除/出队(poll、take)
poll方法如下
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
这里的poll提供了一个非阻塞的出队操作。当队列为空时,返回null,否则出队并返回出队的元素。
当然,与offer类似,poll还有一个阻塞的重载版本。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
ArrayBlockingQueue还提供了一个专门的出队阻塞方法take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
当队列为null时,调用 notEmpty.await()使当前线程进入阻塞状态。下一次入队时会有notEmpty.signal()唤醒阻塞线程,这个唤醒只能唤醒一个,而且是随机的。
(3)查看队头元素peek
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
返回队头元素,不删除。
(4)其他
由于ArrayBlockingQueue是由数组实现的,除了出队(删除队头元素),当然也可以删除任意位置的元素,所以此类对于删除操作还提供了如下方法
remove(Object o) //查找队列,若存在元素o则删除 removeAt(final int removeIndex) //删除指定位置的元素
ArrayBlockingQueue重写了toString方法,将队列中所有元素用[]括起来,然后元素之间用逗号加空格分开。详细自行阅读源码。
drainTo
//将队列中的所有元素放到集合c中
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
} //将队列中的所有元素放到集合c中
public int drainTo(Collection<? super E> c, int maxElements) {
checkNotNull(c);
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int n = Math.min(maxElements, count);
int take = takeIndex;
int i = 0;
try {
while (i < n) {
@SuppressWarnings("unchecked")
E x = (E) items[take];
c.add(x);
items[take] = null;
if (++take == items.length)
take = 0;
i++;
}
return n;
} finally {
// Restore invariants even if c.add() threw
if (i > 0) {
count -= i;
takeIndex = take;
if (itrs != null) {
if (count == 0)
itrs.queueIsEmpty();
else if (i > take)
itrs.takeIndexWrapped();
}
for (; i > 0 && lock.hasWaiters(notFull); i--)
notFull.signal();
}
}
} finally {
lock.unlock();
}
}
完
java源码阅读ArrayBlockingQueue的更多相关文章
- Java源码阅读的真实体会(一种学习思路)
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...
- Java源码阅读的真实体会(一种学习思路)【转】
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- [收藏] Java源码阅读的真实体会
收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...
- java源码阅读Hashtable
1类签名与注释 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, C ...
- Java源码阅读Stack
Stack(栈)实现了一个后进先出(LIFO)的数据结构.该类继承了Vector类,是通过调用父类Vector的方法实现基本操作的. Stack共有以下五个操作: put:将元素压入栈顶. pop:弹 ...
- java源码阅读LinkedBlockingQueue
1类签名与简介 public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements Blocking ...
- Java源码阅读顺序
阅读顺序参考链接:https://blog.csdn.net/qq_21033663/article/details/79571506 阅读源码:JDK 8 计划阅读的package: 1.java. ...
- Java源码阅读ArrayList
1简介 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAc ...
随机推荐
- Jasper语音助理
1. 介绍 Jasper是一款基于树莓派的开源语音控制助理, 使用Python语言开发. Jasper工作原理主要是设备被动监听麦克风, 当收到唤醒关键字时进入主动监听模式, 此时收到语音指令后进行语 ...
- python学习笔记 可变参数关键字参数**kw相关学习
在Python中可以定义可变参数,顾名思义,可变参数就是传入参数是可变的.可以是任意个,以一个简单的数学编程为例,计算 sum = a * a + b * b + .....z * z 函数定义可以如 ...
- arm处理器中a5 a8 a9,v6 v7,arm7 arm9 arm11都是依据什么来分类的【转】
转自:http://blog.csdn.net/maochengtao/article/details/9951131ARM处理器发展这么多年,有很多架构,很多不同的内核 架构有armv1 v2 v3 ...
- 项目问题整理(it)
1,很(屎)优(一)雅(样)的IE9兼容问题: --webuploader在webkit浏览器中自动使用h5上传,但在IE中需要配置支持flash,特别注意两点: ①Upload.swf路径问题不正确 ...
- THULAC:一个高效的中文词法分析工具包(z'z)
网址:http://thulac.thunlp.org/ THULAC(THU Lexical Analyzer for Chinese)由清华大学自然语言处理与社会人文计算实验室研制推出的一套中文词 ...
- AC日记——斐波那契数列(升级版) 洛谷 P2626
斐波那契数列(升级版) 思路: 水题: 代码: #include <cmath> #include <cstdio> #include <cstring> #inc ...
- typescript 定义全局变量以及扩展原生js对象
使用“declare global”操作即可. 项目根目录下新建myDeclareFile.d.ts declare global { interface Navigator { mediaSessi ...
- 山东省第八届省赛 A:Return of the Nim(尼姆+威佐夫)
Problem Description Sherlock and Watson are playing the following modified version of Nim game: Ther ...
- C++的Public.lib(Public.dll) : fatal error LNK1112: module machine type 'X86' conflicts with target machine type 'x64'
今天开始编译网游服务器,找前辈借来批处理文件,版本控制上拿下代码,库等一系列资源,尼玛啊,编译出错: Public.lib(Public.dll) : fatal error LNK1112: mod ...
- 洛谷P2419 [USACO08JAN]牛大赛Cow Contest
对于一个能够确定名次的点,可以注意到,对于该点,入度和出度的数量加起来等于N-1(这样还是不够准确的确切的说是,能够到达这个点的数量和这个点能够到达的数量的和 floyd不仅可以求两个点之间的最短路径 ...