java同步包种ArrayBlockingQueue类的分析与理解
前言:
ArrayBlockingQueue类是一个堵塞队列。重要用于多线程操作的条件。
一,官方解释
一个建立在数组之上被BlockingQueue绑定的堵塞队列。这个队列元素顺序是先进先出。队列的头部是在队列中待的时间最长的元素。队列的尾部是再队列中待的时间最短的元素。新的元素会被插入到队列尾部,而且队列从队列头部获取元素。
这是一个典型的绑定缓冲,在这个缓冲区中。有一个固定大小的数组持有生产者插入的数据。而且消费者会提取这些数据。一旦这个类被创建,那么这个数组的容量将不能再被改变。尝试使用put操作给一个满队列插入元素将导致这个操作被堵塞;尝试从空队列中取元素也会被堵塞。
这个类推荐了一个可选的公平策略来排序等待的生产者和消费者线程。
默认的,这个顺序是不确定的。可是队列会使用公平的设置true来使线程依照先进先出顺序訪问。通常公平性会降低吞吐量可是却降低了可变性以及避免了线程饥饿。
这个类和它的迭代器实现了全部可选的Collection按Iterator接口的方法。
二。源代码分析
先来看看ArrayBlockingQueue类的特殊的一个构造器代码:
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
这个构造器中得參数fair大家注意到没有。正好是传给ReentrantLock的參数,而这个參数正好就是ReentrantLock决定是否为公平或者非公平队列的參数。ReentrantLock參考我的这篇关于java中ReentrantLock类的源代码分析以及总结与样例。
再往下看到,这两个notEmpty和notFull參数实际上是Condition。而Condition能够把它看做一个堵塞信号,Condition的子类ConditionObject(是AbstractQueuedSynchronizer的内部类)拥有两个方法signal和signalAll方法。前一个方法是唤醒队列中得第一个线程,而signalAll是唤醒队列中得全部等待线程,可是仅仅有一个等待的线程会被选择,这两个方法能够看做notify和notifyAll的变体。
在这个堵塞队列的insert和remove方法中都会被调用signal来唤醒等待线程,在put方法中。假设队列已经满了,则会调用await方法来。直到队列有空位,才会调用insert方法插入元素。源码例如以下:
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
事实上设计这个类的程序猿哥哥已经想到了假设我们要用这个类,可是不想在队列满了之后。再插入元素被堵塞。提供了offer方法。这个offer方法有重载方法,调用offer(E e)方法时,假设队列已经满了,那么会直接返回一个false,假设没有满,则直接调用insert插入到队列中;调用offer(E e, long timeout, TimeUnit unit)方法时,会在队列满了之后堵塞队列,可是这里能够由开发者设置超时时间,假设超时时队列还是满的,则会以false返回。源代码例如以下所看到的:
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);
}
insert(e);
return true;
} finally {
lock.unlock();
}
}
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
insert(e);
return true;
}
} finally {
lock.unlock();
}
}
插入数据有堵塞和非堵塞之分,那么提取数据也肯定就有堵塞与非堵塞之分了。
当中take方法是个堵塞方法,当队列为空时,就被堵塞。源代码例如以下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
方法poll是重载方法,跟offer相对,也有基础方法和超时方法之分。
在这个类中还提供了peek方法来提取数据。可是peek方法是从对了的tail提取,而pool是从队列的head提取,即peek提取的元素是进入队列最晚的,而pool提取的元素是进入队列最早时间最长的元素。
再来看看这个类中得迭代器。这个类中的迭代器是线程安全的,为什么会线程安全?由于在实现的next和remove方法中都加了lock了。安全性根本停不下来啊~上源代码:
private class Itr implements Iterator<E> {
private int remaining; // Number of elements yet to be returned
private int nextIndex; // Index of element to be returned by next
private E nextItem; // Element to be returned by next call to next
private E lastItem; // Element returned by last call to next
private int lastRet; // Index of last element returned, or -1 if none
Itr() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
lastRet = -1;
if ((remaining = count) > 0)
nextItem = itemAt(nextIndex = takeIndex);
} finally {
lock.unlock();
}
}
public boolean hasNext() {
return remaining > 0;
}
public E next() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock(); //锁定
try {
if (remaining <= 0)
throw new NoSuchElementException();
lastRet = nextIndex;
E x = itemAt(nextIndex); // check for fresher value
if (x == null) {
x = nextItem; // we are forced to report old value
lastItem = null; // but ensure remove fails
}
else
lastItem = x;
while (--remaining > 0 && // skip over nulls
(nextItem = itemAt(nextIndex = inc(nextIndex))) == null)
;
return x;
} finally {
lock.unlock();
}
}
public void remove() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
int i = lastRet;
if (i == -1)
throw new IllegalStateException();
lastRet = -1;
E x = lastItem;
lastItem = null;
// only remove if item still at index
if (x != null && x == items[i]) {
boolean removingHead = (i == takeIndex);
removeAt(i);
if (!removingHead)
nextIndex = dec(nextIndex);
}
} finally {
lock.unlock();
}
}
}
三,总结
对这个类的描写叙述已经代码分析完毕了,接下来我们来总结一下这个类的一些特点吧:
1.一旦创建,则容量不能再修改
2.这个类是线程安全的,而且迭代器也是线程安全的
3.这个类的put和take方法分别会在队列满了和队列空了之后被堵塞操作。
4.这个类提供了offer和poll方法来插入和提取元素,而不会在队列满了或者队列为空时堵塞操作。
5.这个队列的锁默认是不公平策略,即唤醒线程的顺序是不确定的。
java同步包种ArrayBlockingQueue类的分析与理解的更多相关文章
- 死磕 java集合之ArrayBlockingQueue源码分析
问题 (1)ArrayBlockingQueue的实现方式? (2)ArrayBlockingQueue是否需要扩容? (3)ArrayBlockingQueue有什么缺点? 简介 ArrayBloc ...
- java多线程系列(九)---ArrayBlockingQueue源码分析
java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...
- 并发编程(八)—— Java 并发队列 BlockingQueue 实现之 ArrayBlockingQueue 源码分析
开篇先介绍下 BlockingQueue 这个接口的规则,后面再看其实现. 阻塞队列概要 阻塞队列与我们平常接触的普通队列(LinkedList或ArrayList等)的最大不同点,在于阻塞队列的阻塞 ...
- Java线程池ThreadPoolExecutor类源码分析
前面我们在java线程池ThreadPoolExecutor类使用详解中对ThreadPoolExector线程池类的使用进行了详细阐述,这篇文章我们对其具体的源码进行一下分析和总结: 首先我们看下T ...
- 《手把手教你》系列技巧篇(七十一)-java+ selenium自动化测试-自定义类解决元素同步问题(详解教程)
1.简介 前面宏哥介绍了几种关于时间等待的方法,也提到了,在实际自动化测试脚本开发过程,百分之90的报错是和元素因为时间不同步而发生报错.本文介绍如何新建一个自定义的类库来解决这个元素同步问题.这样, ...
- Java LinkedBlockingQueue和ArrayBlockingQueue分析
LinkedBlockingQueue是一个链表实现的堵塞队列,在链表一头增加元素,假设队列满.就会堵塞.还有一头取出元素.假设队列为空.就会堵塞. LinkedBlockingQueue内部使用Re ...
- Java线程池ThreadPoolExecutor使用和分析(一)
相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...
- java并发包&线程池原理分析&锁的深度化
java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...
- MapReduce剖析笔记之八: Map输出数据的处理类MapOutputBuffer分析
在上一节我们分析了Child子进程启动,处理Map.Reduce任务的主要过程,但对于一些细节没有分析,这一节主要对MapOutputBuffer这个关键类进行分析. MapOutputBuffer顾 ...
随机推荐
- POJ 题目3321 Apple Tree(线段树)
Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 21566 Accepted: 6548 Descr ...
- svn服务器的搭建过程 主要为服务端
yum -y install subversion 查看版本信息 svnserve --version 创建svn仓库目录 mkdir -p /var/svn/svnrepos 创建svn仓库 svn ...
- SPFA的两种优化
SPFA是可以优化的,这个大家都是晓得的吧. 下面介绍两种SPFA的神奇优化(我只代码实现了的一种) SLF:Small Label First策略,设要加入的节点是j,队首元素为i,若dist(j) ...
- SecondaryNameNode合并元信息过程
- Java 类和对象3
编写Java应用程序.首先,定义描述学生的类——Student,包括学号(int).姓名(String).年龄(int)等属性:二个方法:Student(int stuNo,String name,i ...
- 基于JavaSwing的例子-非连接数据库
项目结构: Constant.java package com.mstf.test; import java.io.Serializable; public class Constant implem ...
- PostgreSQL Replication之第二章 理解PostgreSQL的事务日志(3)
2.3 理解一致性和数据丢失 挖掘PostgreSQL事务日志而不考虑一致性是不可能的.在本章的第一部分,我们已经大体上解释了事务日志的基本思想.您已经知道,无需事先的日志改变的能力,使数据处于一种好 ...
- SSD-tensorflow-3 重新训练模型(vgg16)
一.修改pascalvoc_2007.py 生成自己的tfrecord文件后,修改训练数据shape——打开datasets文件夹中的pascalvoc_2007.py文件,根据自己训练数据修改:NU ...
- nginx设置可以默认访问index.php
vim /usr/local/nginx/conf/nginx.conf: 在 location /{ index index.php index.html i ...
- 移动和PC的适配
<script> //mode 移动端的适配方式 按需 传参 目前只有两种 px和rem (function(win, doc, mode) { var std = 750; if(/(i ...