阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
1.ArrayBlockingQueue
ArrayBlockingQueue是一个由数组支持的有界缓存的阻塞队列。在读写操作上都需要锁住整个容器,因此吞吐量与一般的实现是相似的,适合于实现"生产者消费者"模式。ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。这个类是线程安全的。生产者和消费者共用一把锁。
2.LinkedBlockingQueue
基于链表的阻塞队列,内部维持着一个数据缓冲队列(该队列由链表构成)。
只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者线程,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。
LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
ArrayBlockingQueue和LinkedBlockingQueue的区别:
1.队列大小的初始化方式不同
ArrayBlockingQueue是有界的,必须指定队列的大小;
LinkedBlockingQueue是分情况的,指定队列的大小时,就是有界的;不指定队列的大小时,默认是Integer.MAX_VALUE,看成无界队列,但当生产速度大于消费速度的时候,有可能会内存溢出。
2.队列中锁的实现不同
ArrayBlockingQueue实现的队列中的锁时没有分离的,即生产和消费用的是同一个锁;进行put和take操作,通用同一个锁对象。也就是说,put和take无法并行执行!
3.在生产或消费时操作不同
ArrayBlockingQueue基于数组,在插入或删除元素时,是直接将枚举对象插入或移除的,不会产生或销毁任何额外的对象实例
LinkedBlockingQueue基于链表,在插入或删除元素时,需要把枚举对象转换为Node<E>进行插入或移除,会产生一个额外的Node对象,在这长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别,会影响性能。
put()和take()方法
都可以实现阻塞的功能。
put()方法:把元素加入到阻塞队列中,如果阻塞队列没有空间,则调用此方法的线程被阻塞,直到有空间的时候再继续。
take()方法:取出排在阻塞队列首位的对象,若阻塞队列为空,则调用此方法的线程被阻塞,直到有新的对象被加入的时候再继续。
offer()和poll()方法
不具有阻塞的功能
offer():把元素加入到阻塞队列中,如果可以容纳,则返回true。如果不可以容纳,则返回false。
poll()方法:取出排在阻塞队列首位的对象,若阻塞队列为空,则返回null,如果不为空,则返回取出来的那个元素。
3.PriorityBlockingQueue VS PriorityQueue
此阻塞队列为基于数组的无界阻塞队列。它会按照元素的优先级进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,不会阻塞生产者,但会阻塞消费者。PriorityBlockingQueue里面存储的对象必须是实现Comparable接口,队列通过这个接口的compare方法确定对象的priority。
队列的元素并不是全部按照优先级排序的,但是队头的优先级肯定是最高的。每取一个头元素的时候,都会对剩余的元素做一次调整,这样就能保证每次队头的元素都是优先级最高的元素。
4.DelayQueue
DelayQueue是一个无界阻塞队列,用于放置实现了Delayed接口的对象,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed元素。这个队列里面所存储的对象都带有一个时间参数,采用take获取数据的时候,如果时间没有到,取不出来任何数据。而加入数据的时候,是不会阻塞的(不会阻塞生产者,但会阻塞消费者)。DelayQueue内部使用PriorityQueue实现的。DelayQueue是一个使用PriorityQueue实现的BlockingQueue,优先队列的比较基准值是时间。本质上即:DelayQueue = BlockingQueue + PriorityQueue + Delayed。
优势:
如果不使用DelayQueue,那么常规的解决办法就是:使用一个后台线程,遍历所有对象,挨个检查。这种笨笨的方法简单好用,但是对象数量过多时,可能存在性能问题,检查间隔时间不好设置,间隔时间过大,影响精确度,过小则存在效率问题。而且做不到按超时的时间顺序处理。
应用场景:
缓存系统的设计。缓存中的对象,超过了有效时间,需要从缓存中移除。使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
package BlockingQueue.DelayQueueDemo; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; /** * @Author: Soldier49Zed * @Date: 2019/10/21 14:49 * @Description: */ class Wangming implements Delayed { private String name; private String id;//身份证 private long endTime;//截止时间 public Wangming(String name, String id, long endTime) { this.name = name; this.id = id; this.endTime = endTime; } public String getName() { return this.name; } public String getId() { return this.id; } /** * 用来判断是否到了截止时间 */ @Override public long getDelay(TimeUnit unit) { return endTime - System.currentTimeMillis(); } /** * 相互比较排序用 */ @Override public int compareTo(Delayed o) { Wangming jia = (Wangming) o; return endTime - jia.endTime > 0 ? 1 : 0; } } public class Wangba implements Runnable { private DelayQueue<Wangming> queue = new DelayQueue<Wangming>(); public boolean yinye = true; public void shangji(String name, String id, int money) { Wangming man = new Wangming(name, id, 1000 * 60 * money + System.currentTimeMillis()); System.out.println("网名" + man.getName() + " 身份证" + man.getId() + " 交钱" + money + "块,开始上机..."); this.queue.add(man); } public void xiaji(Wangming man) { System.out.println("网名" + man.getName() + " 身份证" + man.getId() + "时间到下机..."); } @Override public void run() { while (yinye) { try { System.out.println("检查ing"); Wangming man = queue.take(); xiaji(man); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { try { System.out.println("网吧开始营业"); Wangba siyu = new Wangba(); Thread shangwang = new Thread(siyu); shangwang.start(); siyu.shangji("路人甲", "123", 1); siyu.shangji("路人乙", "234", 3); siyu.shangji("路人丙", "345", 3); } catch (Exception e) { } } }
5.SynchronousQueue
同步队列是一个不存储元素的队列,它的size()方法总是返回0。每个线程的插入操作必须等待另一个线程的移除操作,同样任何一个线程的移除操作都必须等待另一个线程的插入操作。可以认为SynchronousQueue是一个缓存值为1的阻塞队列。
源码:
ArrayBlockingQueue:
/** The queued items */
final Object[] items;
/** items index for next take, poll, peek or remove */
int takeIndex;
/** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */
int count;
/** 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();
}
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();
}
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;
}
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();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
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 E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
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();
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
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<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
//当队列满时,调用notFull.await()方法释放锁,陷入等待状态。
//有两种情况会激活该线程
//第一、某个put线程添加元素后,发现队列有空余,就调用notFull.signal()方法激活阻塞线程
//第二、take线程获取元素时,发现队列已满。则其取出元素后,也会调用notFull.signal()方法激活阻塞线程
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
//发现队列未满,调用notFull.signal()激活阻塞线程的put线程(可能存在)
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
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;
}
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
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();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}