1 阻塞队列
1.1 常见的阻塞队列
-
ArrayBolckingQueue
由
数组
结构组成的有界
阻塞队列。内部构造是数组,具有数组的特性。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); notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
可以初始化队列⼤⼩, 且⼀旦初始化不能改变。构造⽅法中的fair表示控制对象的内部锁是否采⽤公平锁,默认是
⾮公平锁
。 -
LinkedBlockingQueue
由
链表
结构组成的有界
阻塞队列。具有链表的特性。默认队列的大小是Integer.MAX_VALUE
,也可以指定大小。此队列按照先进先出
的原则对元素进行排序。public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); }
-
DelayQueue
延迟
无界
阻塞队列,该队列中的元素只有当其指定的延迟时间到了
,才能够从队列中获取到该元素。 -
PriorityBlockingQueue
基于优先级的
无界
阻塞队列。二叉堆结构。不会阻塞数据生成者(因为队列是无界的),而只会在没有可消费的数据时,阻塞数据的消费者。 -
SynchronousQueue
同步队列,
内部容量为0
,每个put操作必须等待一个take操作。CachedThreadPool
线程池使用。
SynchronousQueue 使用CAS保证出入队列线程安全,其余队列使用ReentrantLock保证出入队列线程安全。
1.2 阻塞队列的原理
利用了Lock锁的多条件(Condition)阻塞控制。
通过ArrayBlockingQueue JDK 1.8 的源码来了解原理
首先是构造器,除了初始化队列的⼤⼩和是否是公平锁之外,还对同⼀个锁(lock)初始化了两个监视器,分别是notEmpty
和notFull
。这两个监视器的作用目前可以简单理解为标记分组,当该线程是put操作时,给他加上监视器notFull,标记这个线程是⼀个⽣产者;当线程是take操作时,给他加上监视器notEmpty,标记这个线程是消费者。
//数据元素数组
final Object[] items;
//下⼀个待取出元素索引
int takeIndex;
//下⼀个待添加元素索引
int putIndex;
//元素个数
int count;
//内部锁
final ReentrantLock lock;
//消费者监视器
private final Condition notEmpty;
//⽣产者监视器
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
//..省略其他代码
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
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();
}
}
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++;
notEmpty.signal();
}
总结put的流程:
- 所有执⾏put操作的线程竞争lock锁,拿到了lock锁的线程进⼊下⼀步,没有拿到lock锁的线程阻塞等待。
- 判断阻塞队列是否满了,如果满了,则调⽤await⽅法阻塞这个线程,并标记为notFull(⽣产者)线程,同时释放lock锁,等待被消费者线程唤醒。
- 如果没有满,则调⽤enqueue⽅法将元素put进阻塞队列。注意这⼀步的线程还有⼀种情况是第⼆步中阻塞的线程被唤醒且⼜拿到了lock锁的线程。
- 唤醒⼀个标记为notEmpty(消费者)的线程。
take操作的源码
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return 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();
notFull.signal();
return x;
}
总结take的流程
- 所有执⾏take操作的线程竞争lock锁,拿到了lock锁的线程进⼊下⼀步,没有拿到lock锁的线程阻塞等待。
- 判断阻塞队列是否为空,如果是空,则调⽤await⽅法阻塞这个线程,并标记为notEmpty(消费者)线程,同时释放lock锁,等待被⽣产者线程唤醒。
- 如果没有空,则调⽤dequeue⽅法。注意这⼀步的线程还有⼀种情况是第⼆步中阻塞的线程被唤醒且⼜拿到了lock锁的线程。
- 唤醒⼀个标记为notFull(⽣产者)的线程。
1.3 使用场景
生产者-消费者模型
public class TestBlockingQueue {
private int queueSize = 10;
private ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(queueSize);
public static void main(String[] args) {
TestBlockingQueue testBlockingQueue = new TestBlockingQueue();
Thread thread = new Thread(testBlockingQueue.new Consumer());
Thread thread1 = new Thread(testBlockingQueue.new Product());
thread.start();
thread1.start();
}
class Consumer implements Runnable{
@Override
public void run() {
while (true){
try {
blockingQueue.take();
System.out.println("从队列中取走一个元素,队列剩余: "+blockingQueue.size()+" 元素");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Product implements Runnable{
@Override
public void run() {
while (true){
try {
blockingQueue.put(1);
System.out.println("向队列中插入一个元素,队列剩余空间为:"+(queueSize-blockingQueue.size()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
1.4 手写 BlockingQueue
public class BlockingQueue<T> {
// 容器
private List<T> list = new ArrayList<>();
// 锁
private ReentrantLock lock = new ReentrantLock();
// 容量
private int capacity;
// 生产者条件变量
private Condition fullWaitSet = lock.newCondition();
// 消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
BlockingQueue(int capacity){
this.capacity = capacity;
}
public void add(T data){
lock.lock();
try {
while (list.size() == capacity){
try {
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(data);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
public T take(){
lock.lock();
try {
while (list.isEmpty()){
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = list.get(0);
list.remove(0);
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
}