阻塞队列在很多地方会使用到,比如线程池,Zookeeper。一般使用阻塞队列来实现生产中和消费者模型。
什么是队列?
队列是一种基本的数据结构,它是一个只允许在一端进行移除操作,在另一边进行插入操作的线性表,允许插入的一端叫队尾,允许移除的一端成为队头。
什么是阻塞队列?
其实阻塞队列在队列的基础上增加了两个操作。
- 支持阻塞插入:在队列满的情况下,会阻塞继续往队列中添加数据的线程,直到队列中有元素被释放。
- 支持阻塞移除:在队列为空的情况下,会阻塞从队列中获取元素的线程,直到队列中添加了新的元素。
JAVA中提供的阻塞队列
队列名称 | 功能 |
---|---|
ArrayBlockingQueue | 由数组实现的有界队列,按照FIFO的原则对数组进行排序。 |
LinkedBlockingQueue | 由链表实现的有界阻塞队列,默认和最大长度Integer.MAX_VALUE,队列按照先进先出FIFO的原则对数组进行排序。 |
PriorityBlockingQueue | 支持优先级排序的有界阻塞队列,默认按自然顺序的升序;也可以支持自定义实现CompareTo()指定元素顺序排序规则;或者在初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序 |
DelayQueue | 由优先级队列实现的无界队列 |
SynchronousQueue | 不存储元素的阻塞队列,每一个put操作必须要等待一个take操作,否则不能继续添加元素 |
LinkedTransferQueue | 由链表实现的无界阻塞的TransferQueue,相对于其他的阻塞队列,多了tryTransfer()和transfer()方法。 |
LinkedBlockingDeque | 由链表实现的双向阻塞队列,双向阻塞队列的好处是在多线程入队时,减少竞争。 |
阻塞队列中提供的方法
操作 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e, time,unit) |
移除 | remove() | poll | take() | poll(time,util) |
检查 | element() | peek() | 不支持 | 不支持 |
- 抛出异常:如果队列满了,add- IllegalStateException(“Queue full”)。当队列空了,remove-NoSuchElementException。
- 返回特殊值:offer-true/false 。poll-队列空返回null,不为空返回e。
- 一直阻塞:队列满了阻塞put,队列空了阻塞take。
- 超时退出:在阻塞队列加上阻塞的时间。
详解J.U.C中阻塞队列的使用
基于数组结构的阻塞队列ArrayBlockingQueue
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();
}
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();
}
}
基于链表的阻塞队列LinkedBlockingQueue
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);
}
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
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");
enqueue(new Node<E>(e));
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
基于优先级阻塞队列PriorityBlockingQueue
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
public PriorityBlockingQueue(Collection<? extends E> c) {
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
boolean heapify = true; // true if not known to be in heap order
boolean screen = true; // true if must screen for nulls
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();
heapify = false;
}
else if (c instanceof PriorityBlockingQueue<?>) {
PriorityBlockingQueue<? extends E> pq =
(PriorityBlockingQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator();
screen = false;
if (pq.getClass() == PriorityBlockingQueue.class) // exact match
heapify = false;
}
Object[] a = c.toArray();
int n = a.length;
if (c.getClass() != java.util.ArrayList.class)
a = Arrays.copyOf(a, n, Object[].class);
if (screen && (n == 1 || this.comparator != null)) {
for (int i = 0; i < n; ++i)
if (a[i] == null)
throw new NullPointerException();
}
this.queue = a;
this.size = n;
if (heapify)
heapify();
}
延迟阻塞队列DelayQueue
public DelayQueue() {}
public DelayQueue(Collection<? extends E> c) {
this.addAll(c);
}
无存储结构的阻塞队列SynchronousQueue
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
阻塞队列的结合体LinkedTransferQueue
public LinkedTransferQueue() {
}
public LinkedTransferQueue(Collection<? extends E> c) {
this();
addAll(c);
}
双向阻塞队列LinkedBlockingDeque
public LinkedBlockingDeque() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingDeque(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
}
public LinkedBlockingDeque(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock lock = this.lock;
lock.lock(); // Never contended, but necessary for visibility
try {
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (!linkLast(new Node<E>(e)))
throw new IllegalStateException("Deque full");
}
} finally {
lock.unlock();
}
}
阻塞队列的实现原理
阻塞队列的特性:如果队列为空,消费者线程会阻塞,如果队列满了,生产者线程会阻塞。怎么实现?
- 如何让线程在满足某个他特定条件的情况下实现阻塞和唤醒。
- 阻塞队列中的数据应该用什么样的容器来存储。
put()方法的说明
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节点 单向链表结构
Node<E> node = new Node<E>(e);
// 获得锁
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
// 维护链表
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
take()方法说明
take()方法是阻塞获取元素的方法,也就是说当对楼额为空时,会阻塞获取元素的线程:
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.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}