简介
阻塞队列是一种队列,一种可以在多线程环境下使用,并且支持阻塞等待的队列。
- 当队列满的时候,插入元素的线程被阻塞,直达队列不满。
- 队列为空的时候,获取元素的线程被阻塞,直到队列不空。
方法
java的阻塞队列,要实现BlockingQueue
接口:
public interface BlockingQueue<E> extends Queue<E> {
boolean add(E e);
boolean offer(E e);
void put(E e) throws InterruptedException;
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
E take() throws InterruptedException;
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
int remainingCapacity();
boolean remove(Object o);
public boolean contains(Object o);
int drainTo(Collection<? super E> c);
int drainTo(Collection<? super E> c, int maxElements);
}
常用方法的作用理解:
方法 | 抛出异常 | 返回值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方法 | add | offer | put | Offer(time) |
移除方法 | remove | poll | take | Poll(time) |
检查方法 | element | peek | N/A | N/A |
- 抛出异常:当队列满时,如果再往队列里插入元素,会抛出IllegalStateException(“Queuefull”)异常。当队列空时,从队列里获取元素会抛出NoSuchElementException异常。
- 返回特殊值:当往队列插入元素时,会返回元素是否插入成功,成功返回true。如果是移除方法,则是从队列里取出一个元素,如果没有则返回null。
- 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take元素,队列会阻塞住消费者线程,直到队列不为空。
- 超时退出:当阻塞队列满时,如果生产者线程往队列里插入元素,队列会阻塞生产者线程一段时间,如果超过了指定的时间,生产者线程就会退出。
Java中的几种阻塞队列
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
按照先进先出原则,要求设定初始大小 - LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
按照先进先出原则,可以不设定初始大小,Integer.Max_Value
eq:
ArrayBlockingQueue和LinkedBlockingQueue不同:
- 锁上面:ArrayBlockingQueue只有一个锁,LinkedBlockingQueue用了两个锁(插入和读取用了两个锁),
- 实现上:ArrayBlockingQueue直接插入元素,LinkedBlockingQueue需要转换(转为链表节点)。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
默认情况下,按照自然顺序,要么实现compareTo()方法,指定构造参数Comparator - DelayQueue:一个使用优先级队列实现的无界阻塞队列。
支持延时获取的元素的阻塞队列,元素必须要实现Delayed接口。适用场景:实现自己的缓存系统,订单到期,限时支付等等。 - SynchronousQueue:一个不存储元素的阻塞队列。
每一个put操作都要等待一个take操作 - LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
transfer(),必须要消费者消费了以后方法才会返回,tryTransfer()无论消费者是否接收,方法都立即返回。 - LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
可以从队列的头和尾都可以插入和移除元素,实现工作密取,方法名带了First对头部操作,带了last从尾部操作,另外:add=addLast; remove=removeFirst; take=takeFirst
使用
ArrayBlockingQueue
本例生产者两秒中发送一条消息,而消费者一秒钟就会测试获取一条消息,因此消费者阻塞等待。
public class BlockingQueue {
//生产者线程
static class SCZThread extends Thread {
private ArrayBlockingQueue<String> arrayBlockingQueue;
private int count = 0;
SCZThread(ArrayBlockingQueue<String> arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
//生产者每隔2秒中就发送一条消息
while (true) {
try {
Thread.sleep(2000);
//ArrayBlockingQueue的put在插入时,如果阻塞队列已经满了,就会阻塞,因此可能会抛出InterruptedException的中断异常
arrayBlockingQueue.put("来着生产者的第" + (++count) + "条消息");
System.out.println("发送了生产者的第" + count + "条消息");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者线程
static class XFZThread extends Thread {
private ArrayBlockingQueue<String> arrayBlockingQueue;
private int count = 0;
XFZThread(ArrayBlockingQueue<String> arrayBlockingQueue) {
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
//生产者每隔1秒中就等待就收消息
while (true) {
try {
Thread.sleep(1000);
System.out.println("等待接收消息");
//ArrayBlockingQueue的take在获取时,如果阻塞队列是空的,就会阻塞,因此可能会抛出InterruptedException的中断异常
String message = arrayBlockingQueue.take();
System.out.println("收到生产者发送的消息" + message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
//必须指定容量
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
new SCZThread(arrayBlockingQueue).start();
new XFZThread(arrayBlockingQueue).start();
}
}
结果:
DelayQueue
DelayQueue可以放入消息时设置过期时间,过期时间到了,就会弹出,被消费者拿到。这个队列可以用来处理需要过期时间的场景,比如redis中的key过期,订单过期。
由于放进DelayQueue的数据需要实现Delay接口,所以要先对数据进行封装。
public class ItemVo<T> implements Delayed{
private long activeTime;//到期时间,单位毫秒
private T date;
//activeTime是个过期时刻
public ItemVo(long activeTime, T date) {
super();
//传入的时间是毫秒,先转换为纳秒,activeTime保存的是过期时刻,因此还要加上当前系统时间
this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime,
TimeUnit.MILLISECONDS)+System.nanoTime();//将传入的时长转换为超时的时刻
this.date = date;
}
public long getActiveTime() {
return activeTime;
}
public T getDate() {
return date;
}
//按照剩余时间排序
@Override
public int compareTo(Delayed o) {
long d = getDelay(TimeUnit.NANOSECONDS)-o.getDelay(TimeUnit.NANOSECONDS);
return (d==0)?0:((d>0)?1:-1);
}
//返回元素的剩余时间
@Override
public long getDelay(TimeUnit unit) {
long d = unit.convert(this.activeTime-System.nanoTime(),
TimeUnit.NANOSECONDS);
return d;
}
}
消费者:
public class FetchOrder implements Runnable {
private DelayQueue<ItemVo<Order>> queue;
public FetchOrder(DelayQueue<ItemVo<Order>> queue) {
super();
this.queue = queue;
}
@Override
public void run() {
while(true) {
try {
ItemVo<Order> item = queue.take();
Order order = (Order)item.getDate();
System.out.println("get from queue:"+order.getOrderNo());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者:
public class PutOrder implements Runnable {
private DelayQueue<ItemVo<Order>> queue;
public PutOrder(DelayQueue<ItemVo<Order>> queue) {
super();
this.queue = queue;
}
@Override
public void run() {
//5秒到期
Order ordeTb = new Order("Tb12345",366);
ItemVo<Order> itemTb = new ItemVo<Order>(5000,ordeTb);
queue.offer(itemTb);
System.out.println("订单5秒后到期:"+ordeTb.getOrderNo());
//8秒到期
Order ordeJd = new Order("Jd54321",366);
ItemVo<Order> itemJd = new ItemVo<Order>(8000,ordeJd);
queue.offer(itemJd);
System.out.println("订单8秒后到期:"+ordeJd.getOrderNo());
}
}
源码分析
ArrayBlockingQueue
成员变量
//保存消息的数组,初始化时候需要指定该数组的长度
final Object[] items;
//下次插入数据时的下标位置
int takeIndex;
//下次获取数据时的下标位置
int putIndex;
//当前保存的消息数量
int count;
//锁
final ReentrantLock lock;
//数组不为空时的通知Condition
private final Condition notEmpty;
//数组还没满时的通知Condition
private final Condition notFull;
返回值插入offer
往队列加入一个消息,加入成功返回true,失败返回false。
public boolean offer(E e) {
checkNotNull(e); //参数校验
final ReentrantLock lock = this.lock;
lock.lock(); //拿锁
try {
if (count == items.length) //队列已经满了,返回false
return false;
else {
enqueue(e); //队列没有满,调用enqueue入队
return true;
}
} finally {
lock.unlock(); //释放锁
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x; //putIndex插入位置下标
if (++putIndex == items.length) //判断是否已经超出最大下标,一个循环
putIndex = 0;
count++; //保存的消息加一
notEmpty.signal(); //notEmpty Condition通知正在等待获取消息的线程
}
抛出异常插入add
插入失败时返回IllegalStateException(“Queue full”),成功返回true
public boolean add(E e) {
return super.add(e); //调用了父类的add
}
//父类的add
public boolean add(E e) {
if (offer(e)) //调用了offer方法
return true;
else
throw new IllegalStateException("Queue full"); //抛出异常
}
一直阻塞插入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(); //释放锁
}
}
超时退出插入Offer(time)
队列满时,插入阻塞,超过超时时间则返回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) //判断是否过期
return false;
nanos = notFull.awaitNanos(nanos); //阻塞,该地方被唤醒要两种情况,一种时队列没满时,被通知,一种时超时。如果是超时,这是会这一次执行while条件进入并被判断为过期,返回false
}
enqueue(e);
return true;
} finally {
lock.unlock(); //释放锁
}
}
阻塞获取poll
尝试从队列中获取消息,然后获取不到就返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock(); //拿锁
try {
return (count == 0) ? null : dequeue(); //队列为空时,返回null,否则调用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
队列中没有消息时,获取会阻塞
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await(); //阻塞,等待非空通知
return dequeue();
} finally {
lock.unlock();
}
}
超时获得poll(long timeout, TimeUnit unit)
获取超时返回null
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) { //条件判断
if (nanos <= 0) //过期判断
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
LinkedBlockingQueue
LinkedBlockingQueue的实现与ArrayBlockingQueue基本一致,LinkedBlockingQueue的内部使用链表存储消息,所以放进队列中的消息会被先封装成为Node。
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
ArrayBlockingQueue只用了一个锁,无论时获取还是插入,这样对效率是有影响的,获取时不能插入,插入时不能获取。LinkedBlockingQueue使用两把锁,在效率上是有提升的。
/** 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();
DelayQueue
DelayQueue最特殊的地方就是可以通过take获得到期的消息
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //上锁
try {
for (;;) {
E first = q.peek(); //队列头消息
if (first == null) //队列时空的
available.await(); //等待有消息通知
else {
long delay = first.getDelay(NANOSECONDS); //拿到剩余的过期时间
if (delay <= 0) //已经过期,直接出队
return q.poll();
first = null; //线程等待的时候不要持有对头对象
if (leader != null) //leader表示已经有其他线程先调用了take方法,所以当前线程要先进行等待
available.await(); //available表示当没有其他线程在等待时的通知,因为一次只通知一个线程,当通知到这个线程时,表示该线程可以进行操作,不会有多个线程竞争的情况
else {
Thread thisThread = Thread.currentThread(); //获取当前线程
leader = thisThread; //设置为正在操作的线程
try {
available.awaitNanos(delay); //等待消息的过期时间,保证下次循环的时候,对头那个消息一定时超时过期的
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal(); //唤醒下一个在等待的线程
lock.unlock(); //解锁
}
}