BlockingQueue接口定义了一种队列,这种队列通常容量是提前固定(确定了容量大小)的。容量满时往BlockingQueue中添加数据时会造成阻塞,容量为空时取元素操作会阻塞。
我们可以认为BlockingQueue队列是一个水库。水库满了的时侯,上游的水就要被拦住,不能再往水库里灌了。平时农庄浇灌,生活饮用都用这里面的水。如果水库干了,那么要灌溉的工人只能等着上游放水后,才能继续工作。
实际上我们已经用Condition实现过一次了,见《利用Condition来实现阻塞队列》。
BlockingQueue多用于生产-消费模式。还记得Future兑换月饼的例子吗?里面用到的CompletionService的实现用的就是一个保存Future对象的BlockingQueue
在JUC中,BlockingQueue接口有以下实现:
ArrayBlockingQueue基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量putIndex和takeIndex,分别标识着队列的头部和尾部在数组中的位置。
我们用ArrayBlockingQueue改写一下领月饼的程序:
public class ArrayBlockingQueueDemo {
public static void main(String[] args) {
final BlockingQueue<Integer> abqueue=new ArrayBlockingQueue<Integer>(10);
/* 定义生产者:用来做月饼的Callable */
final Runnable runnable = new Runnable() {
public void run() {
try {
/*模拟耗时操作,需要5秒*/
Thread.sleep(5000);
/*在队列中放上一盒做好的月饼编号*/
abqueue.put(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
/*开启线程B--消费者:获取月饼*/
Runnable constmor_runnable=new Runnable() {
public void run() {
try {
ExecutorService tPool = Executors.newCachedThreadPool();
System.out.println("老板,给我开始做月饼...");
/*启动线程A--生产者:运行耗时操作,三条生产线开始生产月饼*/
for(int i=0;i<3;i++){
tPool.submit(runnable);
}
/*拿月饼*/
System.out.println("5秒钟后......");
for(int i=0;i<3;i++){
/*生产月饼需要5秒,这时线程运行到take()这里,还没有月饼,因此就在这里阻塞等待。*/
System.out.println("用月饼券兑换到月饼,该盒月饼编号:"+abqueue.take());
}
System.out.println("拿饼回家...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
new Thread(constmor_runnable).start();
}
}
另外再说说LinkedBlockingQueue:
LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。
它与ArrayBlockingQueue与使用一样,上例中可以直接用LinkedBlockingDeque替换
附:
BlockingQueue接口常用方法:
放入数据:
- offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
- offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
- put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
获取数据:
- poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
- poll(long timeout, TimeUnitunit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
- take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
- drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。