java线程并发库之--线程阻塞队列ArrayBlockingQueue
在前面一篇名为条件阻塞Condition的应用的博客中提到了一个拔高的例子:利用Condition来实现阻塞队列。其实在java中,有个叫ArrayBlockingQueue<E>
的类提供了阻塞队列的功能,所以我们如果需要使用阻塞队列,完全没有必要自己去写。
ArrayBlockingQueue<E>
实现了BlockingQueue<E>
,另外还有LinkedBlockingQueue<E>
和PriorityBlockingQueue<E>
。ArrayBlockingQueue<E>
主要是基于数组实现的,LinkedBlockingQueue<E>
是基于链表实现的,它们都是先进先出;而PriorityBlockingQueue<E>
是基于优先级队列的。这篇博文主要用ArrayBlockingQueue<E>
举例,另外两个使用方式和ArrayBlockingQueue<E>
差不多,具体的可以参考官方文档。
使用ArrayBlockingQueue<E>
需要先指明缓存区的大小,指明之后就无法修改,试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。下面使用ArrayBlockingQueue<E>
来实现类似于前面那篇博客中提到的一个功能:
public class BlockingQueueTest { public static void main(String[] args) { final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3); //缓冲区允许放3个数据 for(int i = 0; i < 2; i ++) { new Thread() { //开启两个线程不停的往缓冲区存数据 @Override public void run() { while(true) { try { Thread.sleep((long) (Math.random()*1000)); System.out.println(Thread.currentThread().getName() + "准备放数据" + (queue.size() == 3?"..队列已满,正在等待":"...")); queue.put(1); System.out.println(Thread.currentThread().getName() + "存入数据," + "队列目前有" + queue.size() + "个数据"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }.start(); } new Thread() { //开启一个线程不停的从缓冲区取数据 @Override public void run() { while(true) { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "准备取数据" + (queue.size() == 0?"..队列已空,正在等待":"...")); queue.take(); System.out.println(Thread.currentThread().getName() + "取出数据," + "队列目前有" + queue.size() + "个数据"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }.start(); } }
程序的逻辑很简单,不难理解,下面看一下运行的结果:
Thread-0准备放数据…
Thread-0存入数据,队列目前有1个数据
Thread-1准备放数据…
Thread-1存入数据,队列目前有2个数据
Thread-2准备取数据…
Thread-2取出数据,队列目前有1个数据
Thread-0准备放数据…
Thread-0存入数据,队列目前有2个数据
Thread-1准备放数据…
Thread-1存入数据,队列目前有3个数据
Thread-2准备取数据…
Thread-2取出数据,队列目前有2个数据
Thread-0准备放数据…
Thread-0存入数据,队列目前有3个数据
Thread-1准备放数据..队列已满,正在等待
Thread-0准备放数据..队列已满,正在等待
Thread-2准备取数据…
Thread-2取出数据,队列目前有2个数据
Thread-1存入数据,队列目前有3个数据
所以使用阻塞队列就非常方便了,不用我们人为的去做判断了。
之前在博客里介绍过线程间通信可以使用synchronized和wait、notify组合,可以使用Condition,其实我们也可以使用阻塞队列来实现线程间的通信,下面举个示例:
//阻塞队列实现线程间通信 public class BlockingQueueCommunication { public static void main(String[] args) { Business bussiness = new Business(); new Thread(new Runnable() {// 开启一个子线程 @Override public void run() { for (int i = 1; i <= 3; i++) { bussiness.sub(); } } }).start(); // main方法主线程 for (int i = 1; i <= 3; i++) { bussiness.main(); } } } class Business { private int i = 0; BlockingQueue queue1 = new ArrayBlockingQueue(1); BlockingQueue queue2 = new ArrayBlockingQueue(1); { try { queue2.put(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void sub() { try { queue1.put(1); //如果主线程没执行完,则子线程缓冲区一直有数,子线程在这里被阻塞 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("子线程执行前i=" + i++); System.out.println("子线程执行后i=" + i); try { queue2.take(); //取出主线程中缓冲区的数,让主线程执行 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void main() { try { queue2.put(1); //如果子线程没执行完,则主线程缓冲区一直有数,主线程在这里被阻塞 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("主线程执行前i=" + i++); System.out.println("主线程执行后i=" + i); try { queue1.take(); //取出子线程中缓冲区的数,让子线程执行 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
代码看起来有点长,但是逻辑很简单,就是主线程和子线程你一下我一下,轮流执行,执行的任务就是将公共数据i自增1,使用阻塞队列实现的,而且线程安全,因为一个线程执行的时候,另一个线程是被阻塞的。
设计思想是这样的,定义两个阻塞队列,分别只能放1个数,分别对应两个线程。那么我在静态代码块中先将主线程的队列塞满,然后我让两个线程在执行任务之前,先往各自的队列中塞一个数,那么肯定主线程肯定被阻塞,因为我之前已经在静态代码块中给主线程的队列塞过一个数了。然后子线程在执行完任务后,把主线程队列中的数取出,那么主线程就开始执行了,子线程此时被阻塞(因为刚刚它自己执行任务前塞了个数),主线程执行完拿出子线程队列中的数,这时候子线程又开始执行了。所以利用了阻塞队列会阻塞一个线程的办法来实现两个线程之间交替执行。
关于阻塞队列的使用,就总结这么多吧!