Java线程(篇外篇):阻塞队列BlockingQueue

       好久没有写文章了,这段时间事情比较杂,工作也比较杂乱,上周日刚搬完家,从自建房搬到了楼房,提升了一层生活品质,哈哈!不过昨天晚上在公交车上钱包被偷了,前段时间还丢个自行车,不得不感叹,京城扒手真多,还无人处理。言归正传,这一段时间我的工作主要是改进公司的调度器,调度器调度线程池执行任务,生产者生产任务,消费者消费任务,那么这时就需要一个任务队列,生产者向队列里插入任务,消费者从队列里提取任务执行,调度器里是通过BlockingQueue实现的队列,随后小查一下,下面看看BlockingQueue的原理及其方法。

       BlockingQueue最终会有四种状况,抛出异常、返回特殊值、阻塞、超时,下表总结了这些方法:

抛出异常 特殊值 阻塞 超时 插入 add(e) offer(e) put(e) offer(e, time, unit) 移除 remove() poll() take() poll(time, unit) 检查 element() peek() 不可用 不可用

       BlockingQueue是个接口,有如下实现类:

       1. ArrayBlockQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象必须明确大小,像数组一样。

       2. LinkedBlockQueue:一个可改变大小的阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象如果没有明确大小,默认值是Integer.MAX_VALUE。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。 

       3. PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。

       4. SynchronousQueue:同步队列。同步队列没有任何容量,每个插入必须等待另一个线程移除,反之亦然。

       下面使用ArrayBlockQueue来实现之前实现过的生产者消/费者模式,代码如下:

/** 定义一个盘子类,可以放鸡蛋和取鸡蛋 */
public class BigPlate {

	/** 装鸡蛋的盘子,大小为5 */
	private BlockingQueue<Object> eggs = new ArrayBlockingQueue<Object>(5);
	
	/** 放鸡蛋 */
	public void putEgg(Object egg) {
		try {
			eggs.put(egg);// 向盘子末尾放一个鸡蛋,如果盘子满了,当前线程阻塞
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 下面输出有时不准确,因为与put操作不是一个原子操作
		System.out.println("放入鸡蛋");
	}
	
	/** 取鸡蛋 */
	public Object getEgg() {
		Object egg = null;
		try {
			egg = eggs.take();// 从盘子开始取一个鸡蛋,如果盘子空了,当前线程阻塞
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 下面输出有时不准确,因为与take操作不是一个原子操作
		System.out.println("拿到鸡蛋");
		return egg;
	}
	
	/** 放鸡蛋线程 */
	static class AddThread extends Thread {
		private BigPlate plate;
		private Object egg = new Object();

		public AddThread(BigPlate plate) {
			this.plate = plate;
		}

		public void run() {
			plate.putEgg(egg);
		}
	}

	/** 取鸡蛋线程 */
	static class GetThread extends Thread {
		private BigPlate plate;

		public GetThread(BigPlate plate) {
			this.plate = plate;
		}

		public void run() {
			plate.getEgg();
		}
	}
	
	public static void main(String[] args) {
		BigPlate plate = new BigPlate();
		// 先启动10个放鸡蛋线程
		for(int i = 0; i < 10; i++) {
			new Thread(new AddThread(plate)).start();
		}
		// 再启动10个取鸡蛋线程
		for(int i = 0; i < 10; i++) {
			new Thread(new GetThread(plate)).start();
		}
	}
}

       执行结果:

放入鸡蛋
放入鸡蛋
放入鸡蛋
放入鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
拿到鸡蛋
拿到鸡蛋
放入鸡蛋
放入鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
拿到鸡蛋
拿到鸡蛋
拿到鸡蛋
拿到鸡蛋

       从结果看,启动10个放鸡蛋线程和10个取鸡蛋线程,前5个放入鸡蛋的线程成功执行,到第6个,发现盘子满了,阻塞住,这时切换到取鸡蛋线程执行,成功实现了生产者/消费者模式。java.util.concurrent包是个强大的包!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏知识分享

红外接收控制灯亮灭

先上图 功能 红外接收头接收到红外信号,控制一个灯翻转 ? 使用74HC123是因为红外接收头接收的是一连串的高低电平,,在接收到第一个电平的跳变沿的时候就控制...

2776
来自专栏Danny的专栏

【J2SE快速进阶】——多线程之synchronized

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

702
来自专栏精讲JAVA

Java 并发集合的实现原理

可以用原子方式更新int值。类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供...

984
来自专栏Java学习网

Java阻塞队列线程集控制的实现方法

Java阻塞队列线程集控制的实现方法 队列以一种先进先出的方式管理数据。如果你试图向一个已经满了的阻塞队列中添加一个元素,或是从一个空的阻塞队列中移除一个元素...

2368
来自专栏博客园迁移

乐观锁与悲观锁

乐观锁与悲观锁 http://www.cnblogs.com/qjjazry/p/6581568.html

863
来自专栏后台及大数据开发

API接口设计:防参数篡改+防二次请求

API接口由于需要供第三方服务调用,所以必须暴露到外网,并提供了具体请求地址和请求参数

1172
来自专栏linux驱动个人学习

详解SPI中的极性CPOL和相位CPHA

SPI由于接口相对简单(只需要4根线),用途算是比较广泛,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。即...

912
来自专栏Ryan Miao

java并发编程实践学习(2)--对象的组合

先验条件(Precondition):某些方法包含基于状态的先验条件。例如,不能从空队列中移除一个元素,在删除元素前队列必须处于非空状态。基于状态的先验条件的操...

34414
来自专栏码匠的流水账

聊聊kafka client chunkQueue 与 MaxLag值

前面一篇文章讨论了ConsumerFetcherManager的MaxLag与ConsumerOffsetChecker的lag值的区别。但是关于MaxLag的...

461
来自专栏进击的程序猿

raft 系列解读(4) 之 etcd-raft学习

大多数Raft的实现都是整体设计,包括存储处理,消息序列化和网络传输,但是本raft库在实现的时候只实现了最核心的算法,换来了灵活性和性能,网络和disk IO...

894

扫码关注云+社区