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

相关文章

来自专栏无题

ThreadLocal详解

ThreadLocal与synchronized的区别到底在哪里 synchronized这类线程同步的机制可以解决多线程并发问题,在这种解决方案下,多个线程...

2965
来自专栏haifeiWu与他朋友们的专栏

死磕Java之聊聊ThreadLocal源码(基于JDK1.8)

记得在一次面试中被问到ThreadLocal,答得马马虎虎,所以打算研究一下ThreadLocal的源码

751
来自专栏xingoo, 一个梦想做发明家的程序员

通过两个小栗子来说说Java的sleep、wait、notify、notifyAll的用法

线程是计算程序运行的最小载体,由于单个单核CPU的硬件水平发展到了一定的瓶颈期,因此就出现了多核多CPU的情况,直接就导致程序员多线程编程的复杂。由此可见线程...

1978
来自专栏Java帮帮-微信公众号-技术文章全总结

Java多线程详解5【面试+工作】

Java多线程详解【面试+工作】 Java线程:新特征-信号量 Java的信号量实际上是一个功能完毕的计数器,对控制一定资源的消费与回收有着很重要的意义,信号量...

38610
来自专栏武培轩的专栏

剑指Offer-用两个栈实现队列

package StackAndQueue; import java.util.Stack; /** * 用两个栈实现队列 * 用两个栈来实现一个队列,...

3266
来自专栏陈满iOS

iOS线程相关·零散知识点集合计划(更新中)

893
来自专栏程序员互动联盟

linux设备驱动第五篇:驱动中的并发与竟态

综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。 首先什么是并发与竟态呢?并发(concurr...

32810
来自专栏携程技术中心

干货 | 深入分析Object.wait/notify实现机制

作者简介 占军,就职携程地面业务部,负责地面搜索服务开发。Java爱好者,知识分享工作者。 最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提供一个稳...

36412
来自专栏奔跑的蛙牛技术博客

java并发知识点(3)-同步

锁是可以重入的,线程可以重复获得已经持有的锁,一个被锁保护的代码可以调用另一个使用相同锁的方法

512
来自专栏Java架构沉思录

ThreadLocal父子线程数据传递方案

介绍InheritableThreadLocal之前,假设对 ThreadLocal 已经有了一定的理解,比如基本概念、原理。在讲解之前我们先列举有关Threa...

1063

扫码关注云+社区