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 条评论
登录 后参与评论

相关文章

来自专栏函数式编程语言及工具

Akka(6): become/unbecome:运算行为切换

   通过一段时间的学习了解,加深了一些对Akka的认识,特别是对于Akka在实际编程中的用途方面。我的想法,或者我希望利用Akka来达到的目的是这样的:作为传...

2709
来自专栏跟着阿笨一起玩NET

多线程什么时候该加锁?

1 .加锁、解锁(同步/互斥)是多线程中非常基本的操作,但我却看到不少的代码对它们处理的很不好。简单说来有三类问题,一是加锁范围太大,虽然避免了逻辑错误,但锁了...

6111
来自专栏Android-薛之涛

Android -建造者模式

  今天我们来聊一聊我们经常用到的模式-建造者模式(Builder Pattern).

963
来自专栏Keegan小钢

Android项目重构之路:实现篇(一)

前两篇文章《Android项目重构之路:架构篇》和《Android项目重构之路:界面篇》已经讲了我的项目开始搭建时的架构设计和界面设计,这篇就讲讲具体怎么实现的...

1033
来自专栏生信宝典

Bookdown文档生成教程

bookdown是一款及其方便的编写技术文档或教材的工具,语法简洁,数据处理灵活。支持Rmarkdown或普通markdown通过pandoc软件转换为HTML...

4135
来自专栏安恒网络空间安全讲武堂

nox&amp;CSAW部分pwn题解

暑假的时候遇到了一群一起学习安全的小伙伴,在他们的诱劝下,开始接触国外的CTF比赛,作为最菜的pwn选手就试着先打两场比赛试试水,结果发现国外比赛真有意思哎嘿。

1303
来自专栏Java学习网

Java Web Response对象的27个方法及状态码

response表示HttpServletResponse对象,主要将JSP容器处理后的结果传回到客户端。 ? 网络配图 1、void addCookie(...

2677
来自专栏数据结构与算法

P3381 【模板】最小费用最大流

题目描述 如题,给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用。 输入输出格式 输入格式: 第...

3397
来自专栏Elasticsearch实验室

Elasitcsearch 底层系列 Lucene 内核解析之 Doc Value

       Elasticsearch 支持行存和列存,行存用于以文档为单位顺序存储多个文档的原始内容,在 Elasitcsearch 底层系列 Lucene...

1884
来自专栏移动开发

ButterKnife和Realm同时引用的小问题

622

扫码关注云+社区