一:ArrayBlockingQueue简介
一个由循环数组支持的有界阻塞队列。它的本质是一个基于数组的BlockingQueue的实现。 它的容纳大小是固定的。此队列按 FIFO(先进先出)原则对元素进行排序。 队列的头部是在队列中存在时间最长的元素。队列的尾部是在队列中存在时间最短的元素。
新元素插入到队列的尾部,队列检索操作则是从队列头部开始获得元素。 这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。 一旦创建了这样的缓存区,就不能再增加其容量。
此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。 默认情况下,不保证是这种排序。然而通过在构造函数将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程(其公平性是通过ReentrantLock来实现公平锁的)。 公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。
特点:
- 它是有界阻塞队列。它是数组实现的,是一个典型的“有界缓存区”。数组大小在构造函数指定,而且从此以后不可改变。
- 它是线程安全的,是阻塞的。
- 不接受 null 元素
- 公平性 (fairness)可以在构造函数中指定。
- 试图向已满队列中放入元素会导致放入操作受阻塞,直到BlockingQueue里有新的唤空间才会被醒继续操作。
- 试图从空队列中检索元素将导致类似阻塞,直到BlocingkQueue进了新货才会被唤醒。
- 其容量在构造函数中指定。容量不可以自动扩展,也没提供手动扩展的接口。
- 它实现了BlockingQueue接口。
- 此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选方法。
ArrayBlockingQueue的几种构造函数:
public ArrayBlockingQueue(int capacity)
public ArrayBlockingQueue(int capacity, boolean fair)
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)
fair如果为true,则按照 FIFO 顺序访问插入或移除时受阻塞线程的队列;如果为 false,则访问顺序是不确定的。
二:ArrayBlockingQueue源码分析
一个基本数组的阻塞队列。可以设置列队的大小。
它的基本原理实际还是数组,只不过存、取、删时都要做队列是否满或空的判断。然后加锁访问。
1:ArrayBlockingQueue的lock
首先成员变量有一个Lock和两个Condition的定义及初始化过程如下:
ArrayBlockingQueue
的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制。ArrayBlockingQueue是一个带有长度的阻塞队列,初始化的时候必须要指定队列长度,且指定长度之后不允许进行修改。
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
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)”的类型。fair为true,表示是公平锁;fair为false,表示是非公平锁。notEmpty
和notFull
是锁的两个Condition条件。
Lock
的作用是提供独占锁机制,来保护竞争的资源;而Condition是为了更精细的对锁进行控制,但是依赖于lock,通过某个条件对多线程进行控制。
ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。
notEmpty
表示"锁的非空条件"。当某线程想从队列中获取数据的时候,而此时队列中的数据为空,则该线程通过notEmpty.await()
方法进行等待;当其他线程向队列中插入元素之后,就调用notEmpty.signal()
方法进行唤醒之前等待的线程。
同理notFull
表示“锁满的条件“。当某个线程向队列中插入元素,而此时队列已满时,该线程等待,即阻塞通过notFull.wait()
方法;其他线程从队列中取出元素之后,就唤醒该等待的线程,这个线程调用notFull.signal()
方法。
ArrayBlockingQueue使用ReentrantLock
来实现的添加元素原子操作,具体可以看poll和offer中的阻塞awaitNanos(nanos)
是使用了Condition中的AQS中的一个方法。
2:入队
增加元素有多种方法add()
,offer()
,put()
,其中add()
方法调用offer()
方法。所以只用关注offer()
和put()
方法即可,offer()
和put()
不同点是,offer()
在队列满时,直接返回false,不会阻塞写入线程。而put()在队列满时,会一直阻塞写入线程,直到有存储空间可以存放元素。
/**
* 添加一个元素,其实super.add里面调用了offer方法
*/
public boolean add(E e) {
return super.add(e);
}
/**
*加入成功返回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();
}
}
/**
* 如果队列已满的话,就会等待
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//和lock()方法的区别是让它在阻塞时也可抛出异常跳出
try {
while (count == items.length)
notFull.await(); //这里就是阻塞了,要注意。如果运行到这里
标签:队列,lock,元素,阻塞,线程,有界,ArrayBlockingQueue
From: https://blog.csdn.net/weixin_41987908/article/details/139183476