阻塞队列与普通的队列(LinkedList/ArrayList)相比,支持在向队列中添加元素时,队列的长度已满阻塞当前添加线程,直到队列未满或者等待超时;从队列中获取元素时,队列中元素为空 ,会将获取元素的线程阻塞,直到队列中存在元素 或者等待超时。

在JUC包中常用的阻塞队列包含ArrayBlockingQueue/LinkedBlockingQueue/LinkedBlockingDeque等,从结构来看都继承了AbstractQueue实现了BlockingQueue接口(LinkedBlockingDeque是双向阻塞队列,实现的是BlockingDeque接口),在BlockingQueue接口中定义了几个供子类实现的接口,可以分为3部分,puts操作、takes操作、其他操作。

puts操作
add(E e) : 添加成功返回true,失败抛IllegalStateException异常
offer(E e) : 成功返回 true,如果此队列已满,则返回 false(如果添加了时间参数,且队列已满也会阻塞)
put(E e) :将元素插入此队列的尾部,如果该队列已满,则一直阻塞 takes操作
remove(Object o) :移除指定元素,成功返回true,失败返回false
poll() : 获取并移除此队列的头元素,若队列为空,则返回 null(如果添加了时间参数,且队列中没有数据也会阻塞)
take():获取并移除此队列头元素,若没有元素则一直阻塞。
peek() :获取但不移除此队列的头;若队列为空,则返回 null。 other操作
contains(Object o):队列中是否包含指定元素
drainTo(Collection<? super E> c):队列转化为集合

关于阻塞队列,我们主要看LinkedBlockingQueue与ArrayBlockingQueue.

ArrayBlockingQueue

ArrayBlockingQueue是基于数组的、有界的、遵循FIFO原则的阻塞队列,队列初始化时必须指定队列的长度。
这是一个经典的“有界缓冲区”,其中固定大小的数组包含由生产者插入并由消费者提取的元素。创建后,无法更改容量。此类支持用于排序等待生产者和消费者线程的可选公平策略。默认情况下,不保证此顺序。但是,将fairness设置为true构造的队列以FIFO顺序授予线程访问权限。公平性通常会降低吞吐量,但会降低可变性并避免饥饿。

结构

相关变量

final Object[] items; //一个数组,用来存放队列中的变量(队列的基础)
int count; //队列中元素的数量
int takeIndex; //下一次take、poll、remove、peek操作的下标值
int putIndex; //下次add、offer、put操作的下标值

构造函数

ArrayBlockingQueue提供了三个构造函数,在只传递初始化大小值时,默认使用的锁是非公平锁,对比三个不同的构造函数而言,真正初始化队列的构造方法是ArrayBlockingQueue(int capacity, boolean fair)方法,传入集合的构造方法会在调用该方法后将集合遍历存入队列中

public ArrayBlockingQueue(int capacity) {
this(capacity, false);
} public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
//使用同一个锁对象,此处是与LinkedBlockingQueue(使用两个不同的锁来控制添加,取出操作)不同的地方
notEmpty = lock.newCondition();
notFull = lock.newCondition();
} public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) {
this(capacity, fair); final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
//向数组中添加数据
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
//设置下次添加操作对应的数组下标值
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}

offer/add操作

add本质上调用的是offer操作,通过返回值true/false可以判断队列中添加元素是否成功,队列已满返回false

public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
     //队列已满,直接返回false
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;
if (++putIndex == items.length)
putIndex = 0;
count++;
//唤醒take/poll(有时间参数)方法获取数据的线程
notEmpty.signal();
}

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();
}
}

poll操作

队列为空返回null,对于有时间参数的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;
//归零操作
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
//唤醒puts线程
notFull.signal();
return x;
}

take操作

队列为空等待,直到队列中存在元素被唤醒

public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}

peek操作

获取队列中第一个不为空的元素(每次takes操作,或者puts操作都会设置下次takes/puts操作的下标)

public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//通过下标值获取元素
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
} final E itemAt(int i) {
return (E) items[i];
}

remove操作

删除内部元素操作是一种本质上缓慢且具有破坏性的操作,需要将删除元素后的元素统一迁移一个单位,并且在操作过程中会获得锁,对性能有影响,因此不应轻易执行remove操作

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;
     //如果takes下标到达队列最大长度,归零
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// an "interior" remove
// slide over all others up through 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 {
          //移动完成,设置puts操作下标
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
//唤醒put操作等待的线程
notFull.signal();
}

综上,ArrayBlockingQueue队列的逻辑并不复杂,但需要注意一下几点

1.ArrayBlockingQueue是以数组来实现队列功能的,在执行puts或者takes操作时一旦下一个操作的下标值大于队列的长度,类中用来记录存取下标值会归零,已达到循环使用队列的目的

2.ArrayBlockingQueue是通过一个锁控制takes以及puts操作,说明在同一时间内只能执行takes操作或者puts操作中的一种,对于阻塞队列来说,保证了线程安全,
但是会影响队列的消费和生产效率,并发性会下降 3.ArrayBlockingQueue在执行remove操作时,会将整个数组进行移动(最坏情况下),同时还会获得锁,对性能的影响比较大

LinkedBlockingQueue

LinkedBlockingQueue是基于链表的、有界的、遵循FIFO原则的阻塞队列,队列默认的最大长度为Integer.MAX_VALUE

结构

重要属性

private final int capacity;//队列的大小,可以自定义,默认为Integer.MAX_VALUE
private final AtomicInteger count = new AtomicInteger(); //当前队列中元素的数量
//take、poll操作需要持有的锁,LinkedBlockingQueue支持并发操作,对于从队列中获取数据需要加锁(会阻塞,ConcurrentLinkedQueue/ConcurrentLinkedDeque
是使用CAS操作来控制的,不会出现阻塞的问题)
private final ReentrantLock takeLock = new ReentrantLock();
//put、offer操作需要持有的锁,同上
private final ReentrantLock putLock = new ReentrantLock();
//notEmpty条件对象,当队列没有数据时用于挂起执行删除的线程
private final Condition notEmpty = takeLock.newCondition();
//notFull条件对象,当队列数据已满时用于挂起执行添加的线程
private final Condition notFull = putLock.newCondition();

Node类

相对于其他(ConcurrentLinkedQueue/ConcurrentLinkedDeque)类来说,LinkedBlockingQueue的Node类要简单好多,由于是基于单链表实现的,只有一个next属性(保存后继节点),一个item属性(存放值),一个构造函数

static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}

构造函数

LinkedBlockingQueue提供了三个构造函数,在不传参数的情况下,默认队列的大小为Integer.MAX_VALUE,对比三个不同的构造函数而言,真正初始化 队列的构造方法是LinkedBlockingQueue(int capacity)方法,传入集合的构造方法会在调用该方法后将集合遍历存入队列中

public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
} public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
//设置队列大小,new一个null节点,head、tail节点指向改节点
this.capacity = capacity;
last = head = new Node<E>(null);
} public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
//获取put、offer操作需要的锁,可重入
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
//将队列的last节点指向该节点
enqueue(new Node<E>(e));
++n;
}
//队列元素计数
count.set(n);
} finally {
putLock.unlock();
}
}

offer操作

通过返回值true/false判断是否成功,offer操作当队列满后并不会阻塞(有时间参数的offer操作也会阻塞),而是直接返回false,put操作是没有返回值的,并且会一直阻塞,等待被唤醒(或者超过时间抛出异常)

public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
//是否超过最大值
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
//获取锁
putLock.lock();
try {
//再次判断队列是否存放满(可能存在多线程的情况)
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
//如果队列没有放满,唤醒下一个添加线程
if (c + 1 < capacity)
//其实这个地方,唤醒的添加线程是执行put方法(或者offer有时间参数的操作)时被阻塞的线程,如果仅仅只是执行offer操作应该不会执行任何操作,
没有对应的添加线程添加到条件队列中(个人理解,也是不太理解的地方)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
//当c=0时,表明当前队列中存在一个元素,通知消费线程去消费
signalNotEmpty();
return c >= 0;
} private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
//唤醒条件等待队列中的消费者去消费数据
notEmpty.signal();
} finally {
takeLock.unlock();
}
}

put操作

put方法不会像offer方法那样去检查队列大小是否超过设定值,put操作一个元素入队列时,如果队列已满,当前线程会进入nofull的条件等待队列中等待,直到队列中元素个数小于队列大小时被唤醒,才继续put操作

public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
//队列已满存放线程阻塞,等待被唤醒
while (count.get() == capacity) {
notFull.await();
}
//入队列
enqueue(node);
c = count.getAndIncrement();
//队列没满,唤醒notfull等待队列中的添加线程
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
//当c=0时,表明当前队列中存在一个元素,通知消费线程去消费
signalNotEmpty();
}

poll操作

必定会有返回值(异常除外),但包含null(队列中没有数据),与take操作比较可以发现,take操作在队列中没有数据时执行take操作的线程会被挂起,直到队列中有数据(有时间参数的poll操作也会被挂起,等待唤醒或者超时)

public E poll() {
final AtomicInteger count = this.count;
//如果队列没有数据,返回null
if (count.get() == 0)
return null;
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
//唤醒其他消费线程
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
} //出队列操作,因为队列的head节点为null节点,在出队列时,会始终保持head节点为空,next节点为真正意义上的首节点
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
//自指向,该节点已经无用,便于GC
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}

take操作

take操作不会向poll操作去检查队列中有没有数据,队列中没有数据时会被挂起,等待被唤醒

public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
//队列中是否有数据,没有等待
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
//如果队列中有数据存在,唤醒notempty等待队列中的消费线程
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
//唤醒添加线程
signalNotFull();
return x;
}

peek操作

获取队列中头部元素,可能存在其他线程执行的删除、take(poll)操作,所以要加锁,获取数据不一定准确

public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
//head节点不存放数据,所以取的是next节点
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}

remove操作

在执行删除操作时,会将puts以及takes操作都上锁,保证线程安全,然后执行遍历删除操作,在删除后,会去唤醒等待中的添加线程执行 添加操作

public boolean remove(Object o) {
if (o == null) return false;
fullyLock();
try {
//遍历单项链表
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
//移除数据
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
} void unlink(Node<E> p, Node<E> trail) {
// assert isFullyLocked();
// p.next is not changed, to allow iterators that are
// traversing p to maintain their weak-consistency guarantee.
p.item = null;
trail.next = p.next;
if (last == p)
last = trail;
//唤醒添加线程
if (count.getAndDecrement() == capacity)
notFull.signal();
}

综上,LinkedBlockingQueue的api与ArrayBlockingQueue并无太大差别,在实现思想上,LinkedBlockingQueue使用了锁分离以及链表,其他与ArraBlockingQueue(一个锁统一管理、数组)没太大区别

LinkedBlockingQueue与ArrayBlockingQueue异同

1.LinkedBlockingQueue是基于链表实现的初始化是可以不用指定队列大小(默认是Integer.MAX_VALUE);ArrayBlockingQueue是基于数组实现的初始化时必须指定队列大小

2.LinkedBlockingQueue在puts操作都会生成新的Node对象,takes操作Node对象在某一时间会被GC,可能会影响GC性能;ArrayBlockingQueue是固定的数组长度循环使用,
不会出现对象的产生与回收 3.LinkedBlockingQueue是基于链表的形式,在执行remove操作时,不用移动其他数据;ArrayBlockingQueue是基于链表,在remove时需要移动数据,影响性能 4.LinkedBlockingQueue使用两个锁将puts操作与takes操作分开;ArrayBlockingQueue使用一个锁来控制,在高并发高吞吐的情况下,LinkedBlockingQueue的性能较好

LinkedBlockingQueue与ArrayBlockingQueue的更多相关文章

  1. SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue性能测试

    SynchronousQueue.LinkedBlockingQueue.ArrayBlockingQueue性能测试 JDK6对SynchronousQueue做了性能优化,避免对竞争资源加锁,所以 ...

  2. SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue性能测试(转)

    听说JDK6对SynchronousQueue做了性能优化,避免对竞争资源加锁,所以想试试到底平时是选择SynchronousQueue还是其他BlockingQueue. 对于容器类在并发环境下的比 ...

  3. java并发之阻塞队列LinkedBlockingQueue与ArrayBlockingQueue

    Java中阻塞队列接口BlockingQueue继承自Queue接口,并提供put.take阻塞方法.两个主要的阻塞类实现是ArrayBlockingQueue和LinkedBlockingQueue ...

  4. LinkedBlockingQueue和ArrayBlockingQueue的异同

    相同: 1.LinkedBlockingQueue和ArrayBlockingQueue都实现了BlockingQueue接口: 2.LinkedBlockingQueue和ArrayBlocking ...

  5. 【转】简析SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue

    转载地址:http://blog.csdn.net/mn11201117/article/details/8671497 SynchronousQueue SynchronousQueue是无界的,是 ...

  6. Java LinkedBlockingQueue和ArrayBlockingQueue分析

    LinkedBlockingQueue是一个链表实现的堵塞队列,在链表一头增加元素,假设队列满.就会堵塞.还有一头取出元素.假设队列为空.就会堵塞. LinkedBlockingQueue内部使用Re ...

  7. 简析SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue

    SynchronousQueue SynchronousQueue是无界的,是一种无缓冲的等待队列,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加:可以认为Sync ...

  8. ThreadPoolExecutor的三种队列SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue

    SynchronousQueue SynchronousQueue是无界的,是一种无缓冲的等待队列,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加:可以认为Sync ...

  9. ArrayBlockingQueue和LinkedBlockingQueue分析

        JAVA并发包提供三个常用的并发队列实现,分别是:ConcurrentLinkedQueue.LinkedBlockingQueue和ArrayBlockingQueue. Concurren ...

随机推荐

  1. WPF MVVM之INotifyPropertyChanged接口的几种实现方式(转)

    原地址:https://www.cnblogs.com/xiwang/archive/2012/11/25/2787358.html 序言 借助WPF/Sliverlight强大的数据绑定功能,可以比 ...

  2. CentOS7.5安装SVN和可视化管理工具iF.SVNAdmin

    一.安装Apache和PHP 由于iF.SVNAdmin使用php写的,因此我们需要安装php yum install httpd php 二.安装SVN服务器(其中,mod_dav_svn是Apac ...

  3. js实现文本框自动显示两位小数

    转自https://blog.csdn.net/qiji2011/article/details/81270552 1.js: //保留2位小数,如:2,会在2后面补上00.即2.00 functio ...

  4. 清除DNS缓存和刷新DHCP列表

    ipconfig /release 只是释放IP地址,然后还需要ipconfig /renew在重新获取一下 如何清除DNS缓存?开始-运行,如下图所示: 在谈出的对话框中输入“cmd”,如下图所示: ...

  5. CentOS7下安装Python3和PIP3

    为了方便以后编译,所以整合了下配置流程 先查看是否安装了Python3 python3 -V 如果没有安装,则先安装依赖包 yum install zlib-devel bzip2-devel ope ...

  6. [Web] About image: MozJPEG

    Image is quite heavy in web traffic. it is about 53% whole web traffic. It is important to make sure ...

  7. Linux CentOS7 字符集

    CentOS 7字符集的问题与6有点区别,会出现下面问题,查看是中文,vi进入就变成乱码了 生产中修改配置文件  [root@ce1d2002a999 ~]# cat /etc/locale.conf ...

  8. ROM

    ROM 是 read only memory的简称,表示只读存储器,是一种半导体存储器.只读存储器(ROM)是一种在正常工作时其存储的数据固定不变,其中的数据只能读出,不能写入,即使断电也能够保留数据 ...

  9. 虚拟机Linux系统ip查询失败问题

    当用SSH连接Linux需要ip地址,但是不论是通过ipconfig命令,还是通过ip addr命令都无法获取Linux的ip,通过以下方法成功解决了该问题: 1.点击编辑里面的虚拟网络编辑器出现如下 ...

  10. 处理kubernetes 一些比较难删除的资源

    kubernetes 提供了force 的命令在我们删除资源的时候,但是很多时候还是不可以的 一般删除资源的处理 命令 kubectl delete <resource> <reso ...