文章目录
并发容器ArrayBlockingQueue和LinkedBlockingQueue
一、简介
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列
+LinkedBlockingQueue:由链表结构组成的有界阻塞队列 - 两者都是继承了AbstractQueue并且实现了BlockingQueue接口;
- 类图:
[外链图片转存失败(img-NxP1YueI-1565598202837)(https://note.youdao.com/yws/api/personal/file/F92CA1E2BD284336A00345F49C7549A4?method=download&shareKey=648922f8f7db7cffcf36820425799120)]
二、ArrayBlockingQueue
2.1 ArrayBlockingQueue核心属性
/** 队列元素,一个定长数组 */
final Object[] items;
/** 下一个出列的元素下标,列头位置 */
int takeIndex;
/** 下一个入列的元素下标,列尾位置 */
int putIndex;
/** 队列元素数量*/
int count;
/** 锁对象 ArrayBlockingQueue 出列入列都必须获取该锁,两个步骤共用一个锁*/
final ReentrantLock lock;
/** 通知出列的条件,不为空,则可以出列 */
private final Condition notEmpty;
/** 通知入列的条件,没有满,即可以入列 */
private final Condition notFull;
2.2 构造方法
- 默认非公平锁
//默认是非公平的锁,通过可重入锁获得两个Condition对象用于控制出列和入列
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();
}
2.3 入列操作
- add:队尾添加,队满时抛出异常
// ArrayBlockingQueue.java
@Override
public boolean add(E e) {
return super.add(e);
}
// AbstractQueue.java
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
- offer:队尾添加,队满时返回false,有重载方法支持自定义超时
public boolean offer(E e) {
checkNotNull(e); //参数检查
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
if (count == items.length)
return false; //列满返回false
else {
enqueue(e); //入列核心方法
return true;
}
} finally {
lock.unlock(); //锁释放
}
}
- 支持超时的offer:队尾添加,超时后返回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) //如果超时时间小于0了,就不等待了,直接返回false
return false;
nanos = notFull.awaitNanos(nanos); //等待队列有空间的Condition的唤醒,
//awaitNanos返回的为剩余可等待时间,相当于每次等待,都会扣除相应已经等待的时间,是Condition提供的方法,
//比如awaitNanos(15),假设等待了5ms,那么返回的就是10,下一次计入while循环判断10大于0,这时候就会
//调用awaitNanos(10),直到等待超时
}
enqueue(e); //入列核心方法
return true;
} finally {
lock.unlock(); //锁释放
}
}
- put: 队尾添加,队满时阻塞(put会等待notFull Condition的通知,)
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(); //锁释放
}
}
- enqueue方法:入列核心实现方法
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) //若列尾已经到了数组的最后一位,则将列尾指针修改为0,循环数组
putIndex = 0;
count++; //总数加一
notEmpty.signal(); //通知出列线程
}
- PS:上面的方法都是基于dequeue来实现的,dequeue内部会进行指针的移位,队列属性的变更,信号的通知等操作,调用dequeue的方法会加锁,源码比较简单
2.4 出列操作
- poll:获取列头元素,队列为空返回null,有重载方法支持自定义超时,支持超时的poll和支持超时的offer类似
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
- take:获取列头元素,队列为空阻塞(take会等待notEmpty Condition的通知,)
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) //队列为空则阻塞
notEmpty.await();
return dequeue(); // 出列
} finally {
lock.unlock();
}
}
- dequeue:出列核心实现方法
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) //若列头已经到了数组的最后一位,则将列头指针修改为0,循环数组
takeIndex = 0;
count--; //总数减一
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); //通知入列线程
return x;
}
PS:上面的方法都是基于enqueue来实现的,都需要加锁,只是加锁后内部逻辑有一些不同,take会等待notEmpty Condition的通知,源码比较简单
- peek:获取不移除
获取列头元素,队列为空返回null,和poll的区别是不会修改对头指针,poll会将指针后移一位,
2.5 移除操作
- remove(Object o):删除指定元素
public boolean remove(Object o) {
if (o == null) return false; //参数校验
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
//队列为空则直接返回false
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
//从列头开始寻找,若没找到就往后依次找,找到了就返回
do {
if (o.equals(items[i])) {
removeAt(i); //找到了就移除这个元素
return true; //找到了移除并返回true
}
if (++i == items.length) //没找到需要往后移动再找,到了数组尾部需要跳到头部
i = 0;
} while (i != putIndex);
}
return false; //对列是空的直接返回false
} finally {
lock.unlock();
}
}
- removeAt(int index):删除指定位置元素
void removeAt(final int removeIndex) {
final Object[] items = this.items;
//如果需要移除的元素就在列头,那么直接移除即可,比较简单
if (removeIndex == takeIndex) {
// removing front item; just advance
items[takeIndex] = null; //将移除的元素置为null
if (++takeIndex == items.length) //处理列头指针
takeIndex = 0;
count--; //总数减一
if (itrs != null)
itrs.elementDequeued();
} else {
//如果需要删除的元素不在列头,处理稍微复杂一点
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 { //对于原本队尾的位置,需要置为null,因为原本队尾的元素挪到了前面的一个坑位
items[i] = null;
this.putIndex = i; //修改队尾指针
break;
}
}
count--; //总数减一
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
2.6 其他
size:返回队列元素个数
remainingCapacity:放回队列剩余空间大小
contains:判断是否包含某个对象,需要全表加锁扫描,因此效率不高
clear:清除队列
三、LinkedBlockingQueue
3.1 LinkedBlockingQueue核心属性
/** 队列容量The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;
/** 元素个数 Current number of elements */
private final AtomicInteger count = new AtomicInteger();
/**
* Head of linked list.
* Invariant: head.item == null
* 链表头指针
*/
transient Node<E> head;
/**
* Tail of linked list.
* Invariant: last.next == null
* 链表尾指针
*/
private transient Node<E> last;
/** 出列锁Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** 入列锁Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
3.2 构造方法
- 默认构造方法
//默认容量为最大整数
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
3.3 链表节点
static class Node<E> {
E item; //存储的对象
Node<E> next; //后继节点指针
Node(E x) { item = x; } //构造方法
}
3.4 入列操作
- offer:队尾添加,队满时返回false,有重载方法支持自定义超时
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false; //当队列已满,直接返回false
int c = -1;
Node<E> node = new Node<E>(e); // 先创建新的节点
final ReentrantLock putLock = this.putLock; //使用putLock
putLock.lock();
try {
if (count.get() < capacity) { // 加锁后再次判断队列是否已满
enqueue(node); //调用enqueue方法将节点添加到队尾
c = count.getAndIncrement();
if (c + 1 < capacity) //队列没有满就唤醒生产者
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty(); //如果c是0,那么队列中至少有一个元素,这时候就唤醒消费者
return c >= 0; // 比较c的大小,判断是否成功,当c大于-1时则添加操作成功
}
- 支持超时的offer:队尾添加,
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock; //使用putLock
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) { //当队列已满阻塞给定时间
if (nanos <= 0) //当时间消耗完全,操作未成功 返回false
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(new Node<E>(e)); // 调用enqueue方法添加一个新的节点
c = count.getAndIncrement(); //同样调用AtomicInteger的方法
if (c + 1 < capacity)
notFull.signal(); //队列没有满就唤醒生产者
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty(); //如果c是0,那么队列中至少有一个元素,这时候就唤醒消费者
return true; // 操作成功返回true
}
- put: 队尾添加,队满时阻塞(put会等待notFull Condition的通知,)
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();//先检查添加值是否为null
int c = -1; // 必须使用局部变量来表示队列元素数量,负数表示操作失败
Node<E> node = new Node(e); //先创建新的节点
final ReentrantLock putLock = this.putLock; //使用putLock来保证线程安全
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) { //当队列已满,添加线程阻塞
notFull.await();
}
enqueue(node); // 调用enqueue方法添加到队尾
c = count.getAndIncrement(); //调用AtomicInteger的getAndIncrement()是数量加1
if (c + 1 < capacity)//添加成功后判断是否可以继续添加,队列未满
notFull.signal(); //唤醒添加线程
} finally {
putLock.unlock();
}
if (c == 0) // 添加后如果队列中只有一个元素,唤醒一个取出线程,使用取出锁
signalNotEmpty();
}
- enqueue:入列核心实现方法
private void enqueue(Node<E> node) {
last = last.next = node; //在尾部后面追加一个node,并修改last指针为新的队尾
}
3.5 出列操作
- poll:获取列头元素,队列为空返回null,有重载方法支持自定义超时,支持超时的poll和支持超时的offer类似
public E poll() {
final AtomicInteger count = this.count;
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(); //获取当前的元素个数,但是这里的c其实比元素的个数要大1,因为执行了getAndDecrement方法,这里的c相当于是没有弹出元素之前的元素个数
if (c > 1)
notEmpty.signal(); //队列中元素大于1,那么这里就唤醒其他的消费者线程
}
} finally {
takeLock.unlock();
}
if (c == capacity) //如果c等于容量,那说明dequeue一个元素之后,元素个数就是capacity-1,这时候可以唤醒一个生产者线程进行生产
signalNotFull();
return x;
}
/**
* Signals a waiting put. Called only from take/poll.
* 唤醒一个消费者线程,只会被take和poll调用,因为这两个方法会消费一个元素,这里假定这样的场景:因为队列已经满了,N个生产者全部阻塞了,这时候如果消费者take或者poll了
* 一个之后没有唤醒生产者,那么生产者会一直在这里等待
*/
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
- take:获取列头元素,队列为空阻塞(take会等待notEmpty Condition的通知,)
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); /使用takeLock保证线程安全
try {
while (count.get() == 0) { //当队列为空,取出线程阻塞
notEmpty.await();
}
x = dequeue(); //dequeue方法从队头取出元素
c = count.getAndDecrement(); //总数减一
if (c > 1) //判断如果当前队列之前元素的数量大于1,唤醒取出线程
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity) //c等于容量,说明此时真正的元素个数比容量小1,那么就可以唤醒生产者进行生产
signalNotFull();
return x;
}
- dequeue:出列核心实现方法
dequeue方法移除头部元素并往后一定head,代码比较简单。
- peek:获取不移除
获取列头元素,队列为空返回null,和poll的区别是不会修改对头指针,poll会移除队列首部元素并将head后移一位
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
3.6 移除操作
- remove(Object o):删除指定元素
public boolean remove(Object o) {
if (o == null) return false;
fullyLock(); //加锁,将入列锁和出列锁都锁住
try {
//这里p是trail后面的一个节点,如果p的就是需要删除的元素,那么调用unlink方法,将trail指向p后面的一个元素,即相当于删除了p这个元素
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) {
p.item = null;
trail.next = p.next;
if (last == p) //如果p是队尾,那么直接将trail设置为队尾
last = trail;
if (count.getAndDecrement() == capacity) //如果减去p这个元素之前刚好等于容量值,那么去除p之后会多出一个空位,需要唤醒生产者
notFull.signal();
}
3.7 其他
size:返回队列元素个数
remainingCapacity:放回队列剩余空间大小
contains:判断是否包含某个对象,需要全表加锁扫描,因此效率不高
clear:清除队列
四、小结
- ArrayBlockingQueue使用了一个锁,LinkedBlockingQueue使用了2把锁
- ArrayBlockingQueue中生产之后唤醒消费者和消费之后唤醒生产者的操作是在dequeue和enqueue方法中完成的,而LinkedBlockingQueue是在对应的生产或者消费方法的最后一步通过判断来实现唤醒对立角色的。比如消费之后发现有空位出来就唤醒生产者,生产之后发现有一个元素(生产之前可能是0个)就唤醒消费者,并且在生产的时候,若未达到容量则还会唤醒生产者,消费的时候发现还有元素也会唤醒消费者。
五、生产者消费者示例
/**
* @author by mozping
* @Classname Pc
* @Description TODO
* @Date 2019/3/5 20:33
*/
public class Pc {
static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
//BlockingQueue<AtomicInteger> blockingQueue = new ArrayBlockingQueue<>(100);
BlockingQueue<AtomicInteger> blockingQueue = new LinkedBlockingQueue<>(100);
Producer p = new Producer(blockingQueue);
Consumer c1 = new Consumer(blockingQueue);
Consumer c2 = new Consumer(blockingQueue);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
static class Producer implements Runnable {
BlockingQueue<Integer> blockingQueue;
Producer(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true) {
int i = atomicInteger.getAndIncrement();
blockingQueue.add(i);
System.out.println("生产者生产了:" + i);
int ii = atomicInteger.getAndIncrement();
blockingQueue.add(ii);
System.out.println("生产者生产了:" + ii);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer implements Runnable {
BlockingQueue<Integer> blockingQueue;
Consumer(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true) {
try {
int i = blockingQueue.take();
System.out.println("消费者消费了:" + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("消费者被中断了...");
}
}
}
}
class Item {
private String name;
Item(String name) {
this.name = name;
}
}
}
本文详细对比分析了ArrayBlockingQueue和LinkedBlockingQueue两种并发容器的特性、核心属性、构造方法及入列、出列等关键操作,揭示了它们在实现线程安全及生产者消费者模式中的异同。

被折叠的 条评论
为什么被折叠?



