阻塞队列之BlockingQueue

阻塞队列之BlockingQueue

从1.5版本开始,JDK提供了阻塞队列的接口及其不同的实现类,阻塞队列的接口为BlockingQueue。

阻塞队列的定义

那么,先聊聊什么是队列。关于队列的定义,我们看下百度百科对于队列的定义。

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。

从百度百科对队列的定义,可以总结出队列的两个特性:

  • 线性表
  • 先进先出(FIFO),从队列头部获取元素,从队列尾部插入元素

那么,再来聊下什么是阻塞队列。我们看下JDK中对BlockingQueue的定义,如下:

A {@link java.util.Queue} that additionally supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element.
{@code BlockingQueue} methods come in four forms, with different ways of handling operations that cannot be satisfied immediately, but may be satisfied at some point in the future: one throws an exception, the second returns a special value (either {@code null} or {@code false}, depending on the operation), the third blocks the current thread indefinitely until the operation can succeed, and the fourth blocks for only a given maximum time limit before giving up.

阻塞队列是在队列基础上附加了两个操作的队列。附加的两个操作如下:

  • 获取元素的操作会被阻塞直至队列非空
  • 插入元素的操作会被阻塞直至队列非满
阻塞队列支持的操作

BlockingQueue的方法可以划分为四种类型,通过不同的方式实现操作的需求不会很快满足,但可能在未来某个时候满足。四种类型如下:

  • 操作会抛出异常
  • 操作会返回特殊值,null 或者 false
  • 操作会被阻塞直至成功
  • 操作在给定的时间会被阻塞,超时会放弃操作

我们以表格的形式总结如下:

操作类型抛出异常返回特殊值一直阻塞超时
插入add(E e)offer(E e)put(E e)offer(E e, long timeout, TimeUnit unit)
获取remove(Object o)poll()take()poll(long timeout, TimeUnit unit)
检查element()peek()不支持不支持

当然,BlockingQueue对于插入的元素也是有要求的。

A {@code BlockingQueue} does not accept {@code null} elements. Implementations throw {@code NullPointerException} on attempts to {@code add}, {@code put} or {@code offer} a {@code null}. A {@code null} is used as a sentinel value to indicate failure of {@code poll} operations.

BlockingQueue不接受null元素。如果使用add、put、offer方法试图把一个null元素放入阻塞队列时,BlockingQueue的实现类会抛出NullPointerException。在BlockingQueue中,null作为哨兵值表明使用poll方法获取元素失败。

阻塞队列的应用场景

那么阻塞队列的应用场景有哪些呢?

{@code BlockingQueue} implementations are designed to be used primarily for producer-consumer queues, but additionally support the {@link java.util.Collection} interface. So, for example, it is possible to remove an arbitrary element from a queue using {@code remove(x)}. However, such operations are in general not performed very efficiently, and are intended for only occasional use, such as when a queued message is cancelled.

BlockingQueue的实现类主要被设计用来实现生产者-消费者模式队列,但也附加支持Collection接口。例如,有可能使用remove(x)方法删除队列中的任意一个元素。然而,这些操作执行效率通常不高,并且仅用于偶尔使用,例如当队列消息被取消时。

使用阻塞队列实现生产者-消费者模式

那么,如何使用阻塞队列来实现生产者-消费者模式呢?具体实现如下:

/**
 * 使用阻塞队列实现生产者-消费者模式
 * @author zhaoheng
 * @date 2019年5月12日
 */
public class ProducerConsumerPattern {

	public static void main(String[] args) {
		BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
		//生产者线程
		Thread producerThread = new Thread(new Producer(queue));
		//消费者线程
		Thread consumerThread = new Thread(new Consumer(queue));
		producerThread.start();
		consumerThread.start();
	}

}
/**
 * 生产者
 * @author zhaoheng
 * @date 2019年5月12日
 */
class Producer implements Runnable {

	private static final Logger LOG = LoggerFactory.getLogger(Producer.class);
	
	private BlockingQueue<Integer> queue;
	
	public Producer(BlockingQueue<Integer> queue) {
		this.queue = queue;
	}

	@Override
	public void run() {
		int data = 1;
		while (true) {
			try {
				Thread.sleep(500);
				queue.put(data); //生产数据
				LOG.info("生产者生产数据:{}", data);
				data++;
			} catch (InterruptedException e) {
				LOG.error("生产数据异常:", e);
			}
		}
	}
}
/**
 * 消费者
 * @author zhaoheng
 * @date 2019年5月12日
 */
class Consumer implements Runnable {
	
	private static final Logger LOG = LoggerFactory.getLogger(Consumer.class);

	private BlockingQueue<Integer> queue;
	
	public Consumer(BlockingQueue<Integer> queue) {
		this.queue = queue;
	}

	@Override
	public void run() {
		try {
			while (true) {
				//控制消费速度
				Thread.sleep(1000);
				Integer result = queue.take(); //消费数据
				LOG.info("消费者消费数据:{}", result);
			}
		} catch (InterruptedException e) {
			LOG.error("消费数据异常:", e);
		}
	}
}

使用阻塞队列实现生产者-消费者模式的比较简单,只要把握好三个关键点就可以。

  • 定义一个全局的阻塞队列,笔主这里使用了BlockingQueue的实现类之LinkedBlockingQueue,LinkedBlockingQueue是由链表结构组成的有界阻塞队列。
  • 定义一个生产者,实现Runnable接口,同时定义参数为BlockingQueue的构造方法,在run()中将生产的数据插入阻塞队列。
  • 定义一个消费者,实现Runnable接口,同时定义参数为BlockingQueue的构造方法,在run()中从阻塞队列中获取数据进行消费。

代码的运行结果如下:

2019-05-12 14:30:30.333 - 消费者消费数据:1
2019-05-12 14:30:31.347 - 生产者生产数据:2
2019-05-12 14:30:31.347 - 消费者消费数据:2
2019-05-12 14:30:32.347 - 生产者生产数据:3
2019-05-12 14:30:32.347 - 消费者消费数据:3
2019-05-12 14:30:33.347 - 生产者生产数据:4
2019-05-12 14:30:33.347 - 消费者消费数据:4
2019-05-12 14:30:34.348 - 生产者生产数据:5
2019-05-12 14:30:34.348 - 消费者消费数据:5
2019-05-12 14:30:35.349 - 生产者生产数据:6
2019-05-12 14:30:35.349 - 消费者消费数据:6
2019-05-12 14:30:36.349 - 生产者生产数据:7
2019-05-12 14:30:36.349 - 消费者消费数据:7
2019-05-12 14:30:37.349 - 生产者生产数据:8
2019-05-12 14:30:37.349 - 消费者消费数据:8
BlockingQueue的实现类

BlockingQueue为实现阻塞队列提供了基本的定义,其实现类也有多种,这里简单介绍下JDK中已经实现的阻塞队列,如下:

  • ArrayBlockingQueue,基于数组实现的阻塞队列,数据在被移除前保存在数组中。创建时必须指定队列容量,插入(写)或移除(读)元素需要竞争使用同一个lock对象(ReentrantLock)。
  • LinkedBlockingQueue,基于链表实现的阻塞队列,数据在被移除前保存在链表中。创建时可指定容量也可不指定,不指定默认容量是Integer.MAX_VALUE。插入(写)或移除(读)分别使用putLock和takeLock,采用了“读写分离”的思想,吞吐量比ArrayBlockingQueue要好。
  • SynchronousQueue,不保存数据,生产者生产的数据会立即被消费者消费。
  • PriorityBlockingQueue,基于优先级的队列,优先级的判定方式可以在使用构造方法时通过传入Comparator接口的实现类实例来指定。如果创建时不指定队列的容量,默认容量是11,队列会自动扩容,是一个无界队列。
  • DelayQueue,延迟消费的队列。队列元素需要实现Delayed,当元素的延迟时间到了才可以被消费。DelayQueue没有容量限制,是一个无界队列。

至此,对BlockingQueue的介绍完毕。

由于笔主水平有限,笔误或者不当之处还请批评指正。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值