https://blog.csdn.net/javazejian/article/details/77410889
- ArrayBlockingQueue内部的阻塞队列是通过重入锁ReenterLock和Condition条件队列实现的,
- 所以ArrayBlockingQueue中的元素存在公平访问与非公平访问的区别,
- 对于公平访问队列,被阻塞的线程可以按照阻塞的先后顺序访问队列,
- 即先阻塞的线程先访问队列。
- 而非公平队列,当队列可用时,阻塞的线程将进入争夺访问资源的竞争中,
- 也就是说谁先抢到谁就执行,没有固定的先后顺序。
-
//默认非公平阻塞队列 ArrayBlockingQueue queue = new ArrayBlockingQueue(2); //公平阻塞队列 ArrayBlockingQueue queue1 = new ArrayBlockingQueue(2,true);
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** 存储数据的数组 */
final Object[] items;
/**获取数据的索引,主要用于take,poll,peek,remove方法 */
int takeIndex;
/**添加数据的索引,主要用于 put, offer, or add 方法*/
int putIndex;
/** 队列元素的个数 */
int count;
/** 控制并非访问的锁 */
final ReentrantLock lock;
/**notEmpty条件对象,用于通知take方法队列已有元素,可执行获取操作 */
private final Condition notEmpty;
/**notFull条件对象,用于通知put方法队列未满,可执行添加操作 */
private final Condition notFull;
/**
迭代器
*/
transient Itrs itrs = null;
}
- ArrayBlockingQueue内部确实是通过数组对象items来存储所有的数据
- 一个ReentrantLock来同时控制添加线程与移除线程的并发访问,
- 这点与LinkedBlockingQueue区别很大(稍后会分析)
- notEmpty条件对象则是用于存放等待或唤醒调用take方法的线程,
- 告诉他们队列已有元素,可以执行获取操作
- notFull条件对象是用于等待或唤醒调用put方法的线程,
- 告诉它们,队列未满,可以执行添加元素的操作
- 只要putIndex与takeIndex不相等就说明队列没有结束
- 一个ReentrantLock来同时控制添加线程与移除线程的并发访问,
LinkedBlockingQueue
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/**
* 节点类,用于存储数据
*/
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
/** 阻塞队列的大小,默认为Integer.MAX_VALUE */
private final int capacity;
/** 当前阻塞队列中的元素个数 */
private final AtomicInteger count = new AtomicInteger();
/**
* 阻塞队列的头结点
*/
transient Node<E> head;
/**
* 阻塞队列的尾节点
*/
private transient Node<E> last;
/** 获取并移除元素时使用的锁,如take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** notEmpty条件对象,当队列没有数据时用于挂起执行删除的线程 */
private final Condition notEmpty = takeLock.newCondition();
/** 添加元素时使用的锁如 put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** notFull条件对象,当队列数据已满时用于挂起执行添加的线程 */
private final Condition notFull = putLock.newCondition();
}
- 由链表实现的有界队列阻塞队列,但大小默认值为Integer.MAX_VALUE
- 所以我们在使用LinkedBlockingQueue时建议手动传值,
- 为其提供我们所需的大小,
- 避免队列过大造成机器负载或者内存爆满等情况。
- 如果存在添加速度大于删除速度时候,
- 有可能会内存溢出,这点在使用前希望慎重考虑
- 在正常情况下,链接队列的吞吐量要高于基于数组的队列(ArrayBlockingQueue)
- 因为其内部实现添加和删除操作使用的两个ReenterLock来控制并发执行,
- 内部维持一个基于链表的数据队列
- 每个添加到LinkedBlockingQueue队列中的数据都将被封装成Node节点,
- 添加的链表队列中,其中head和last分别指向队列的头结点和尾结点
- 内部分别使用了takeLock 和 putLock 对并发进行控制,
- 也就是说,添加和删除操作并不是互斥操作,
- 可以同时进行,这样也就可以大大提高吞吐量
- remove方法删除指定的对象,为什么同时对putLock和takeLock加锁?
- remove(Object o)
- 删除指定元素
- 这是因为remove方法删除的数据的位置不确定,
- 为了避免造成并非安全问题,所以需要对2个锁同时加锁。
- 其他移除队尾的操作,一个takeLock锁够了
- remove(Object o)