阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加操作支持阻塞地插入和移除方法。支持阻塞插入的方法是指当队列满时会阻塞插入元素的线程,直到队列不满;支持阻塞移除的方法是指当队列为空时获取元素的线程无法继续获取元素直到队列不空。
可以发现阻塞队列非常适合消费者和生产者场景下进行使用,生产者生产数据就是向阻塞队列中插入元素,消费者消费数据就是从阻塞队列中移除元素。
Java提供了阻塞队列支持如下方法:
插入方法:add(e)(添加失败会抛出异常)、offer(e)(添加失败返回特殊值)、put(e)(添加失败会一直阻塞)
移除方法:remove(e)(移除失败会抛出异常)、poll(e)(移除失败会返回特殊值)、take(e)(移除失败会一直阻塞)
在Java中提供了无界队列,这种情况下队列不可能出现满的情况(除非发生内存溢出),所以使用put和take方法永远不会被阻塞,offer返回的永远是true。
在Java中提供了7种阻塞队列,使用较多有四种:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue和DelayQueue。ArrayBlockingQueue是一个由数组结构组成的有界阻塞队列,LinkedBlockingQueue是一个由链表结构组成的有界阻塞队列,PriorityBlockingQueue是一个支持优先级排序的无界阻塞队列,DelayQueue是一个使用优先级队列实现的支持延时获取元素的无界阻塞队列。DelayQueue适用于缓存系统的设计以及定时任务调度等场景。
那么阻塞队列是如何实现线程的同步的呢?使用通知模式实现。
通知模式是指当生产者往满的队列添加元素的时候会阻塞生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列已经不满了,这时生产者可以继续往队列中添加元素。就ArrayBlockingQueue而言,是使用Condition条件变量实现通知模式的。
1public ArrayBlockingQueue(int capacity, boolean fair) { 2 //省略部分代码 3 notEmpty = lock.newCondition(); 4 notFull = lock.newCondition(); 5 } 6//添加元素的方法 7public void put(E e) throws InterruptedException { 8 checkNotNull(e); 9 final ReentrantLock lock = this.lock; 10 lock.lockInterruptibly(); 11 try { 12 while (count == items.length) 13 notFull.await(); 14 //如果队列不满就入队 15 enqueue(e); 16 } finally { 17 lock.unlock(); 18 } 19 } 20 //入队的方法 21 private void enqueue(E x) { 22 final Object[] items = this.items; 23 items[putIndex] = x; 24 if (++putIndex == items.length) 25 putIndex = 0; 26 count++; 27 notEmpty.signal(); 28 } 29 //移除元素的方法 30 public E take() throws InterruptedException { 31 final ReentrantLock lock = this.lock; 32 lock.lockInterruptibly(); 33 try { 34 while (count == 0) 35 notEmpty.await(); 36 return dequeue(); 37 } finally { 38 lock.unlock(); 39 } 40 } 41 //出队的方法 42 private E dequeue() { 43 final Object[] items = this.items; 44 @SuppressWarnings("unchecked") 45 E x = (E) items[takeIndex]; 46 items[takeIndex] = null; 47 if (++takeIndex == items.length) 48 takeIndex = 0; 49 count--; 50 if (itrs != null) 51 itrs.elementDequeued(); 52 notFull.signal(); 53 return x; 54 }
- 从源码可以看出,阻塞队列的实现仍然是使用了经典的等待/通知模式实现的。使用阻塞队列的好处在于使用者不用关心什么时候等待,什么时候进行通知,什么时候添加元素什么时候取元素都由使用者实现,让使用者可以更多关注业务的实现。那么对于上一篇文章提到的生产者消费者模式,如何使用阻塞队列实现呢?
下面代码演示了使用阻塞队列实现生产者消费者模式:
1package com.rhwayfun.concurrency; 2 3import java.util.concurrent.BlockingQueue; 4import java.util.concurrent.LinkedBlockingQueue; 5import java.util.concurrent.TimeUnit; 6 7/** 8 * Created by rhwayfun on 16-4-4. 9 */ 10public class ProducerConsumerModeWithBlockQueueTest { 11 12 static class Info{ 13 //内容 14 private String content; 15 16 public Info(String content) { 17 this.content = content; 18 } 19 20 public String getContent() { 21 return content; 22 } 23 24 public void setContent(String content) { 25 this.content = content; 26 } 27 28 @Override 29 public String toString() { 30 return this.getContent(); 31 } 32 } 33 34 static class Producer implements Runnable{ 35 36 private final BlockingQueue<Info> blockingQueue; 37 38 public Producer(BlockingQueue<Info> blockingQueue) { 39 this.blockingQueue = blockingQueue; 40 } 41 42 public void run() { 43 boolean flag = true; 44 for (int i = 0; i < 5; i++){ 45 if (flag){ 46 try { 47 blockingQueue.put(new Info("contentA")); 48 System.out.println("[生产者]:contentA"); 49 TimeUnit.SECONDS.sleep(1); 50 } catch (InterruptedException e) { 51 e.printStackTrace(); 52 } 53 flag = false; 54 }else { 55 try { 56 blockingQueue.put(new Info("contentB")); 57 System.out.println("[生产者]:contentB"); 58 TimeUnit.SECONDS.sleep(1); 59 } catch (InterruptedException e) { 60 e.printStackTrace(); 61 } 62 flag = true; 63 } 64 } 65 } 66 } 67 68 static class Consumer implements Runnable{ 69 70 private final BlockingQueue<Info> blockingQueue; 71 72 public Consumer(BlockingQueue<Info> blockingQueue) { 73 this.blockingQueue = blockingQueue; 74 } 75 76 public void run() { 77 while (true){ 78 try { 79 System.out.println("[消费者]:" + blockingQueue.take()); 80 TimeUnit.SECONDS.sleep(1); 81 } catch (InterruptedException e) { 82 e.printStackTrace(); 83 } 84 } 85 } 86 } 87 88 public static void main(String[] args){ 89 BlockingQueue<Info> blockingQueue = new LinkedBlockingQueue<Info>(); 90 new Thread(new Producer(blockingQueue)).start(); 91 new Thread(new Consumer(blockingQueue)).start(); 92 } 93}
- 可以发现,相比之前使用等待/通知模式实现的生产者消费者模式,使用阻塞队列实现的代码更加简洁,Info类无需添加任何同步方法,程序的可扩展性提高了提高,耦合度也降低了。