BlockingQueue 阻塞队列
队列就是存放元素的容器,FIFO。
所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
常用于生产者和消费者的场景,生产者---》往队列里添加元素,消费者---》从队列里拿取元素。
在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。
阻塞队列提供了四种处理方法:
方式 | 抛出异常 | 不抛出异常,有返回值 | 阻塞等待 | 超时等待 |
添加 | add() | offer() | put() | offer() |
移除 | remove() | poll() | take() | poll() |
检测队首元素 | element() | peek() | ~ | ~ |
阻塞队列的成员
ArrayBlockingQueue:是一个用数组实现的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行排序。支持公平锁和非公平锁。
LinkedBlockingQueue:一个由链表结构组成的有界队列,此队列的长度为Integer.MAX_VALUE。此队列按照先进先出的顺序进行排序。
SynchronousQueue:一个不存储元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。支持公平锁和非公平锁。
没有容量,一个生产线程,当它生产产品(put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞。等待一个消费线程调用take操作,take操作会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递)。这样的一个过程称为一次配对过程(也可以先take,后put 原理相同)。
ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。
使用举例
生产者
5 6 /** 7 * 生产者线程 8 * 9 * 10 */ 11 public class Producer implements Runnable { 12 13 private volatile boolean isRunning = true;//是否在运行标志 14 private BlockingQueue queue;//阻塞队列 15 private static AtomicInteger count = new AtomicInteger();//自动更新的值 16 private static final int DEFAULT_RANGE_FOR_SLEEP = 1000; 17 18 //构造函数 19 public Producer(BlockingQueue queue) { 20 this.queue = queue; 21 } 22 23 public void run() { 24 String data = null; 25 Random r = new Random(); 26 27 System.out.println("启动生产者线程!"); 28 try { 29 while (isRunning) { 30 System.out.println("正在生产数据..."); 31 Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));//取0~DEFAULT_RANGE_FOR_SLEEP值的一个随机数 32 33 data = "data:" + count.incrementAndGet();//以原子方式将count当前值加1 34 System.out.println("将数据:" + data + "放入队列..."); 35 if (!queue.offer(data, 2, TimeUnit.SECONDS)) {//设定的等待时间为2s,如果超过2s还没加进去返回true 36 System.out.println("放入数据失败:" + data); 37 } 38 } 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 Thread.currentThread().interrupt(); 42 } finally { 43 System.out.println("退出生产者线程!"); 44 } 45 } 46 47 public void stop() { 48 isRunning = false; 49 } 50 }
消费者
5 /** 6 * 消费者线程 7 * 8 * 9 */ 10 public class Consumer implements Runnable { 11 12 private BlockingQueue<String> queue; 13 private static final int DEFAULT_RANGE_FOR_SLEEP = 1000; 14 15 //构造函数 16 public Consumer(BlockingQueue<String> queue) { 17 this.queue = queue; 18 } 19 20 public void run() { 21 System.out.println("启动消费者线程!"); 22 Random r = new Random(); 23 boolean isRunning = true; 24 try { 25 while (isRunning) { 26 System.out.println("正从队列获取数据..."); 27 String data = queue.poll(2, TimeUnit.SECONDS);//有数据时直接从队列的队首取走,无数据时阻塞,在2s内有数据,取走,超过2s还没数据,返回失败 28 if (null != data) { 29 System.out.println("拿到数据:" + data); 30 System.out.println("正在消费数据:" + data); 31 Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP)); 32 } else { 33 // 超过2s还没数据,认为所有生产线程都已经退出,自动退出消费线程。 34 isRunning = false; 35 } 36 } 37 } catch (InterruptedException e) { 38 e.printStackTrace(); 39 Thread.currentThread().interrupt(); 40 } finally { 41 System.out.println("退出消费者线程!"); 42 } 43 } 44 45 46 }
测试
public class BlockingQueueTest { 7 8 public static void main(String[] args) throws InterruptedException { 9 // 声明一个容量为10的缓存队列 10 BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10); 11 12 //new了三个生产者和一个消费者 13 Producer producer1 = new Producer(queue); 14 Producer producer2 = new Producer(queue); 15 Producer producer3 = new Producer(queue); 16 Consumer consumer = new Consumer(queue); 17 18 // 借助Executors 19 ExecutorService service = Executors.newCachedThreadPool(); 20 // 启动线程 21 service.execute(producer1); 22 service.execute(producer2); 23 service.execute(producer3); 24 service.execute(consumer); 25 26 // 执行10s 27 Thread.sleep(10 * 1000); 28 producer1.stop(); 29 producer2.stop(); 30 producer3.stop(); 31 32 Thread.sleep(2000); 33 // 退出Executor 34 service.shutdown(); 35 } 36 }
SynchronizedQueue举例:
package test; import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; public class SynchronousQueueDemo { public static void main(String[] args) { BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); new Thread(()->{ try { System.out.println(Thread.currentThread().getName()+" put 1"); blockingQueue.put("1"); System.out.println(Thread.currentThread().getName()+" put 2"); blockingQueue.put("2"); System.out.println(Thread.currentThread().getName()+" put 3"); blockingQueue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } },"T1").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+"take"+blockingQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+"take"+blockingQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+"take"+blockingQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } },"T2").start(); } } 结果: T1 put 1 T2take1 T1 put 2 T2take2 T1 put 3 T2take3
小结
作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了.
BlockingQueue不光实现了一个完整队列所具有的基本功能,同时在多线程环境下,他还自动管理了多线间的自动等待于唤醒功能,从而使得程序员可以忽略这些细节,关注更高级的功能。