Java并发 阻塞队列

阻塞队列(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类无需添加任何同步方法,程序的可扩展性提高了提高,耦合度也降低了。
点赞
收藏

评论区

加载中...

相关推荐

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java并发数据结构

一.BlockingDeque阻塞双端队列(线程安全):注意ArrayDeque和LinkedList仅仅扩展了Deque,是非阻塞类型的双端队列。BlockingQueue单向队列,其内部基于ReentrantLockCondition来控制同步和"阻塞"/"唤醒"的时

Java多线程之线程安全队列Queue

在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列。Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。注:什么叫线程安全?这个首先要明确。

LinkedBlockingQueue 介绍

LinkedBlockingQueue是一个基于已链接节点的、范围任意的blockingqueue。此队列按FIFO(先进先出)排序元素。队列的头部是在队列中时间最长的元素。队列的尾部是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可

BlockingQueue介绍

几种类型的BlockingQueueArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。DelayQueue:一个使用优先级队列实现的无界阻塞队列。Synchro

Java并发系列9

今天要讲的BlockingQueue可谓是大名鼎鼎,在并发编程中比较常见的一个类。BlockingQueue顾名思义是表示一个阻塞队列,注意这两个词:阻塞和队列。可以拿我们熟悉的生产者消费者队列来举例,一条流水线上,A生产零件,B组装零件,A就是生产者,B是消费者。如果A生成的太快,则零件堆积,A需要休息一会儿等待B把零件消费完;如果A生产的太