Queue API的几种实现详解
Queue API的几种方法的使用
| 方法名称 | 作用 | 描述 |
|---|---|---|
| add | 添加元素到队列 | 如果队列满了就抛异常java.lang.IllegalStateException |
| remove | 移除并且返回队列头部元素 | 如果队列为null,就抛异常java.util.NoSuchElementException |
| element | 返回队列头部元素,不会移除元素 | 如果队列为null,就抛异常java.util.NoSuchElementException |
| offer | 添加元素到队列 | 如果队列满了就返回false,不会阻塞 |
| poll | 移除并且返回队列头部元素 | 如果队列为null,就返回null,不会阻塞 |
| peek | 返回队列头部元素,不会移除元素 | 如果队列为null,就返回null,不会阻塞 |
| put | 添加元素到队列 | 如果队列满了就阻塞 |
| take | 移除并且返回队列头部元素 | 如果队列为null,就阻塞 |
ArrayBlockingQueue原理及源码解析
根据名字,可以知道,ArrayBlockingQueue底层是数组实现的,而且是阻塞的队列,下面看下put元素和take元素时的图解:

上面的图基本上就是ArrayBlockingQueue队列使用时的底层实现原理了,下面根据源码来看一下。
ArrayBlockingQueue的成员变量
/** 存放元素 */
final Object[] items;
/** 记录下一次从什么位置开始取元素或者移除元素 */
int takeIndex;
/** 记录下一次从什么位置开始放元素 */
int putIndex;
/** 队列中元素的数量 */
int count;
/** 可重入锁,用来放元素和取元素时加锁 */
final ReentrantLock lock;
/** 取元素的等待集合 */
private final Condition notEmpty;
/** 存放元素的等待集合 */
private final Condition notFull;
ArrayBlockingQueue的offer和put方法
offer方法源码:
/**
* 存放一个元素到队列
* 如果队列未满,就放入队列的尾部
* 如果队列已满,就返回false
*
* @throws NullPointerException 如果存放的元素是null,抛异常
*/
public boolean offer(E e) {
//校验放入的元素是否为空,每次放入元素到队列,都会校验
checkNotNull(e);
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果队列中的元素已经满了,就返回false
if (count == items.length)
return false;
else {
//未满就调用方法,放入元素到队列
enqueue(e);
return true;
}
} finally {
//释放锁
lock.unlock();
}
}
put方法源码:
/**
* 存放一个元素到队列
* 如果队列未满,就放入队列的尾部
* 如果队列已满,就阻塞等待
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
//校验放入的元素是否为空,每次放入元素到队列,都会校验
checkNotNull(e);
//加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果队列中的元素已经满了,就阻塞,挂起当前线程
//这里使用while而不使用if,是为了防止伪唤醒
while (count == items.length)
notFull.await();
//未满就调用方法,放入元素到队列
enqueue(e);
} finally {
//释放锁
lock.unlock();
}
}
参数校验:
private static void checkNotNull(Object v) {
//如果传入的元素是null,就抛异常
if (v == null)
throw new NullPointerException();
}
共同调用的enqueue方法:
/**
* 将指定的元素放入队列的尾部
* 只有持有锁才可以调用
*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
//获取队列中的元素
final Object[] items = this.items;
//putIndex就是要放入的元素在队列中的的索引
items[putIndex] = x;
//如果放入元素之后,队列满了,就把putIndex置为0
//意思是下一次向队列中放元素,就是放入队列的第一个位置了
//putIndex的作用就是记录下一次元素应该放到哪里
if (++putIndex == items.length)
putIndex = 0;
//元素的个数加一
count++;
//唤醒拿元素没有拿到挂起的线程,告诉它:
//元素已经放入了队列,可以取元素了
notEmpty.signal();
}
ArrayBlockingQueue的poll和take方法
poll方法源码:
/**
* 从队列中拿元素
* 如果队列中没有元素了,就返回null
* 如果队列中有元素,就返回
*/
public E poll() {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果队列中没有元素,就返回Null
//否则就调用方法取元素然后返回
return (count == 0) ? null : dequeue();
} finally {
//释放锁
lock.unlock();
}
}
take方法源码:
/**
* 从队列中拿元素
* 如果队列中没有元素了,就阻塞
* 如果队列中有元素,就返回
*/
public E take() throws InterruptedException {
//加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果队列中没有元素,就让线程阻塞
//使用while而不使用if,是为了防止伪唤醒
while (count == 0)
//挂起线程
notEmpty.await();
//如果队列中有元素存在,就取出返回
return dequeue();
} finally {
//释放锁
lock.unlock();
}
}
dequeue方法源码:
/**
* 从队列中取元素
* 只有持有锁才可以调用
*/
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
//获取队列中的元素
final Object[] items = this.items;
@SuppressWarnings("unchecked")
//获取要取出的是哪一个元素
E x = (E) items[takeIndex];
//取出后设置为Null
items[takeIndex] = null;
//要是取出的是队列中的最后一个元素
//就把takeIndex置为0,意思是下一次取元素从队列第一个开始取
if (++takeIndex == items.length)
takeIndex = 0;
//队列中元素的个数减一
count--;
if (itrs != null)
itrs.elementDequeued();
//唤醒因为存放元素时,队列满了,挂起的线程
//告诉它,可以存放元素了
notFull.signal();
//返回取出的元素
return x;
}
ArrayBlockingQueue的peek方法
/**
* 返回队列头部的元素
* 如果队列中没有元素了,就返回null
* 如果队列中有元素,就返回
*/
public E peek() {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//有元素就返回,没有就返回null
return itemAt(takeIndex); // null when queue is empty
} finally {
//释放锁
lock.unlock();
}
}
注意:实例化ArrayBlockingQueue时必须指定队列的容量大小,否则会编译错误
ArrayBlockingQueue<String> queue =
new ArrayBlockingQueue<String>(3);
LinkedBlockingDeque原理及源码解析
根据名字,可以知道LinkedBlockingDeque,底层是使用链表的方式存储元素的,而且是阻塞队列,初始化一个LinkedBlockingDeque可以不用指定队列的容量,即可以指定一个无界的队列。
LinkedBlockingDeque的成员变量
private static final long serialVersionUID = -387911632671998426L;
/** 链表节点类 */
static final class Node<E> {
/**
* 链表中的元素
*/
E item;
/**
* 上一个节点
*/
Node<E> prev;
/**
* 下一个节点
*/
Node<E> next;
//构造函数
Node(E x) {
item = x;
}
}
/**
* 指向第一个节点的指针
*/
transient Node<E> first;
/**
* 指向最后一个节点的指针
*/
transient Node<E> last;
/** 队列中元素的个数 */
private transient int count;
/** 队列的容量 */
private final int capacity;
/** 可重入锁 */
final ReentrantLock lock = new ReentrantLock();
/** 取元素的等待集合 */
private final Condition notEmpty = lock.newCondition();
/** 存放元素的等待集合 */
private final Condition notFull = lock.newCondition();
LinkedBlockingDeque的构造函数
/**
* 无参构造函数
* 可以创建一个无界的队列,队列容量是int的最大值
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingDeque() {
this(Integer.MAX_VALUE);
}
/**
* 创建一个指定边界的队列
* 队列的大小由编码时指定
* @param capacity 指定的队列容量值
* @throws IllegalArgumentException if {@code capacity} is less than 1
*/
public LinkedBlockingDeque(int capacity) {
//如果传入的指定队列容量值小于0,就抛异常
if (capacity <= 0) throw new IllegalArgumentException();
//指定的队列容量大小
this.capacity = capacity;
}
/**
* 创建一个包含指定集合元素的队列
*
* @param c 要包含这个元素的集合
* @throws NullPointerException 如果指定的集合或者其中的元素是null,抛异常
*/
public LinkedBlockingDeque(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
//加锁
final ReentrantLock lock = this.lock;
lock.lock(); // Never contended, but necessary for visibility
try {
//遍历指定的集合,然后放入队列
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (!linkLast(LinkedBlockingDeque.Node<E>(e)))
throw new IllegalStateException("Deque full");
}
} finally {
//释放锁
lock.unlock();
}
}
LinkedBlockingDeque的offer和put方法
offer方法源码:
/**
* 添加一个元素到队列
* 队列未满,就直接添加进去
* 队列已满,就返回false
* @throws NullPointerException 添加元素为null。抛异常
*/
public boolean offer(E e) {
return offerLast(e);
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean offerLast(E e) {
//如果添加的元素是null,就抛异常
if (e == null) throw new NullPointerException();
//初始化一个链表,把元素放入链表
Node<E> node = new Node<E>(e);
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
return linkLast(node);
} finally {
//释放锁
lock.unlock();
}
}
/**
* 把元素添加到链表,如果队列元素满了,就返回false
*/
private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread();
//如果添加进去元素,队列的长度大于队列的容量,就返回false
if (count >= capacity)
return false;
//获取链表的最后一个节点
Node<E> l = last;
//把链表的最后一个节点做为上一个节点
node.prev = l;
//把当前要添加的元素放入链表
last = node;
//如果链表的第一个节点是null,就把元素放入链表的第一个位置
if (first == null)
first = node;
else
//否则就把元素放入链表最后一个节点的下一个节点中
l.next = node;
//队列中元素的个数加一
++count;
//唤醒拿元素时阻塞的线程
notEmpty.signal();
//添加成功,返回true
return true;
}
put方法源码:
/**
* 添加一个元素到队列
* 队列未满,就直接添加进去
* 队列已满,就阻塞
* @throws NullPointerException {@inheritDoc}
* @throws InterruptedException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
putLast(e);
}
/**
* @throws NullPointerException {@inheritDoc}
* @throws InterruptedException {@inheritDoc}
*/
public void putLast(E e) throws InterruptedException {
//如果添加的元素是null,就返回NullPointerException
if (e == null) throw new NullPointerException();
//初始化一个链表,把元素放入链表
Node<E> node = new Node<E>(e);
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//当元素没有添加成功,就挂起
//linkLast方法在上面offer中已经写了
while (!linkLast(node))
notFull.await();
} finally {
//释放锁
lock.unlock();
}
}
LinkedBlockingDeque的poll和take方法
poll方法源码:
/**
* 从队列中取元素
* 队列有元素存在,取出元素
* 队列为空,返回null
*/
public E poll() {
return pollFirst();
}
public E pollFirst() {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//队列有元素就返回,否则就返回null
return unlinkFirst();
} finally {
//释放锁
lock.unlock();
}
}
take方法源码:
/**
* 从队列中取元素
* 队列有元素存在,取出元素
* 队列为空,就阻塞
*/
public E take() throws InterruptedException {
return takeFirst();
}
public E takeFirst() throws InterruptedException {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
E x;
//当队列为空时,就阻塞
//while防止伪唤醒
while ( (x = unlinkFirst()) == null)
notEmpty.await();
//队列有元素存在,返回取出的元素
return x;
} finally {
//释放锁
lock.unlock();
}
}
unlinkFirst方法源码:
/**
* Removes and returns first element, or null if empty.
* 删除并返回第一个元素,如果为空则返回null
*/
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
//获取队列中第一个元素
//及链表的第一个节点
Node<E> f = first;
//第一个元素为null,就返回null
if (f == null)
return null;
//获取第一个节点的下一个节点
Node<E> n = f.next;
//获取第一个节点的元素值
E item = f.item;
//把值设置为null
f.item = null;
f.next = f; // help GC
//把队列的第一个元素设置为:
//要移除元素的下一个节点
first = n;
//如果是null,就把最后一个节点设置为null
if (n == null)
last = null;
else
//否则就把上一个节点设置为Null
n.prev = null;
//队列的元素个数减一
--count;
//唤醒存放元素时,挂起的线程
notFull.signal();
//返回取出的元素
return item;
}
LinkedBlockingDeque的peek方法
/**
* 返回队列头部的元素
* 队列有元素存在,返回元素
* 队列为空,返回null
*/
public E peek() {
return peekFirst();
}
public E peekFirst() {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果队列头部元素为空就返回null
//否则就返回头部元素
return (first == null) ? null : first.item;
} finally {
//释放锁
lock.unlock();
}
}
ConcurrentLinkedDeque
根据英文意思,可以知道,ConcurrentLinkedDeque是一个非阻塞的队列,底层是链表实现,具体源码请查看其他文章。
SynchronousQueue的简单使用
SynchronousQueue是一个容量为0的队列,队列内部不存储元素;当put一个元素时,如果没有take方法去拿元素,就会一直阻塞,直到有take方法去拿元素才会结束;同样的,take元素时,如果没有put元素,那么就会一直阻塞,直到有put元素,才会结束,下面看下示例:
public static void main(String[] args) {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Thread th = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("start work...");
//向队列放入元素
queue.offer("hello");
System.out.println("end work...");
}
});
th.start();
//打印取出的元素
System.out.println(queue.poll());
//打印结果为null
try {
//打印结果为hello
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
从上面的例子可以看出,在SynchronousQueue队列中,offer进去的元素可能会丢失。
SynchronousQueue<String> queue = new SynchronousQueue<>();
Thread th = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("start work...");
//向队列放入元素
try {
//会阻塞
queue.put("hello");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end work...");
}
});
th.start();
try {
//打印结果为hello
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
put元素时,如果没有take方法去拿元素就会阻塞;如果这时使用poll方法去拿元素,取出的元素是null,而且不会结束阻塞。
take元素时,如果没有put或者offer元素进队列,也会阻塞。
PriorityQueue的简单使用
PriorityQueue是一个优先级队列,会自动对元素进行排序,也可以自己指定排序规则。
public static void main(String[] args) {
PriorityQueue<String> queue = new PriorityQueue<String>();
//入队列
queue.offer("36");
queue.offer("21");
queue.offer("57");
queue.offer("78");
queue.offer("22");
//出队列
System.out.println(queue.poll());//21
System.out.println(queue.poll());//22
System.out.println(queue.poll());//36
System.out.println(queue.poll());//57
System.out.println(queue.poll());//78
}
PriorityQueue还可以自定义排序规则,通过实现compare方法即可:
public static void main(String[] args) {
PriorityQueue<Student> queue = new PriorityQueue<Student>(10, new Comparator<Student>() {
//自定义比较规则
@Override
public int compare(Student o1, Student o2) {
if (o1.age > o2.age) {
return 1;
} else {
return -1;
}
}
});
//入队列
queue.offer(new Student("小明", 15));
queue.offer(new Student("小红", 12));
queue.offer(new Student("小黑", 16));
//出队列
System.out.println( queue.poll().age);//12
System.out.println(queue.poll().age);//15
System.out.println(queue.poll().age);//16
}
static class Student {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
结束语
本文只讲了两个队列的源码,可能存在不足或者不够深入的地方,还希望有朋友可以多多指正,其他几个队列的源码,后续有时间的话再做解析,感谢阅读!
Queue API的几种实现详解的更多相关文章
- 利用C#实现AOP常见的几种方法详解
利用C#实现AOP常见的几种方法详解 AOP面向切面编程(Aspect Oriented Programming) 是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 下面这篇文章主要 ...
- rabbitmq五种模式详解(含实现代码)
一.五种模式详解 1.简单模式(Queue模式) 当生产端发送消息到交换机,交换机根据消息属性发送到队列,消费者监听绑定队列实现消息的接收和消费逻辑编写.简单模式下,强调的一个队列queue只被一个消 ...
- redis 五种数据结构详解(string,list,set,zset,hash)
redis 五种数据结构详解(string,list,set,zset,hash) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存 ...
- Java构造和解析Json数据的两种方法详解二
在www.json.org上公布了很多JAVA下的json构造和解析工具,其中org.json和json-lib比较简单,两者使用上差不多但还是有些区别.下面接着介绍用org.json构造和解析Jso ...
- [转]hibernate三种状态详解
本文来自 http://blog.sina.com.cn/u/2924525911 hibernate 三种状态详解 (2013-04-15 21:24:23) 转载▼ 分类: hibernate ...
- android emulator启动的两种方法详解
android emulator启动的两种方法详解 转https://blog.csdn.net/TTS_Kevin/article/details/7452237 对于android学习者,模 ...
- 解决C#程序只允许运行一个实例的几种方法详解
解决C#程序只允许运行一个实例的几种方法详解 本篇文章是对C#中程序只允许运行一个实例的几种方法进行了详细的分析介绍,需要的朋友参考下 本文和大家讲一下如何使用C#来创建系统中只能有该程序的一个实例运 ...
- redis 五种数据结构详解(string,list,set,zset,hash),各种问题综合
redis 五种数据结构详解(string,list,set,zset,hash) https://www.cnblogs.com/sdgf/p/6244937.html redis 与 spring ...
- 多表连接的三种方式详解 hash join、merge join、 nested loop
在多表联合查询的时候,如果我们查看它的执行计划,就会发现里面有多表之间的连接方式.多表之间的连接有三种方式:Nested Loops,Hash Join 和 Sort Merge Join.具体适用哪 ...
随机推荐
- Window下Scala开发环境搭建
在Windows下搭建Scala开发环境,需要做以下几个步骤 1) 安装JDK 2) 安装Scala,并配置环境变量 3) Idea安装并创建Scala 类 1.安装JDK JDK安装,这里不再介绍, ...
- 鸿蒙js开发7 鸿蒙分组列表和弹出menu菜单
鸿蒙入门指南,小白速来!从萌新到高手,怎样快速掌握鸿蒙开发?[课程入口]目录:1.鸿蒙视图效果2.js业务数据和事件3.页面视图代码4.跳转页面后的视图层5.js业务逻辑部分6.<鸿蒙js开发& ...
- Linux安装与使用
1.安装 1.1安装VMware 1.1.1VM12版本安装 1)下载:网盘:链接:https://pan.baidu.com/s/1Jnr--KIy3bSTvRhtB8nfiQ 提取码:czna 2 ...
- Java并发之ThreadPoolExecutor源码解析(三)
Worker 先前,笔者讲解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在这个方法中工作线程可能创建成功,也可能创建 ...
- dapr学习:dapr介绍
该部分主要是给出学习dapr的入门,描述dapr全貌告诉你dapr是啥以及介绍dapr的主要功能与组件 该部分分为两章: 第一章:介绍dapr 第二章:调试dapr的解决方案项目 1. 介绍dapr ...
- POJ-2195(最小费用最大流+MCMF算法)
Going Home POJ-2195 这题使用的是最小费用流的模板. 建模的时候我的方法出现错误,导致出现WA,根据网上的建图方法没错. 这里的建图方法是每次到相邻点的最大容量为INF,而花费为1, ...
- C++ 中的虚函数表及虚函数执行原理
为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚 ...
- 2020年12月-第02阶段-前端基础-Day06
CSS Day06 定位(position) 理解 能说出为什么要用定位 能说出定位的4种分类 能说出四种定位的各自特点 能说出我们为什么常用子绝父相布局 应用 能写出淘宝轮播图布局 1. CSS 布 ...
- 14. vue源码入口+项目结构分析
一. vue源码 我们安装好vue以后, 如何了解vue的的代码结构, 从哪里下手呢? 1.1. vue源码入口 vue的入口是package.json 来分别看看是什么含义 dependences: ...
- 通达OA 任意文件上传-2013/2015版本
参考 http://wiki.0-sec.org/0day/%E9%80%9A%E8%BE%BEoa/11.html 影响版本 2013版本 2015版本 漏洞文件 general/vmeet/wbU ...