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.具体适用哪 ...
随机推荐
- JUnit5学习之三:Assertions类
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- 分布式事务 SEATA-1.4.1 AT模式 配合NACOS 应用
SEATA 配置 目录 SEATA 配置 TC (Transaction Coordinator) - 事务协调者 配置参数 nacos bash 脚本 同步 config 配置到 nacos 使用 ...
- 基于3.X版本的脚手架创建VUE项目
一.基于交互式命令行的方式,创建vue项目 1.命令:vue create 项目名称.项目名称必须是英文的.不要包含中文.特殊的字符和符号.在cmd中输入命令:vue create vue_proje ...
- 浮动引发的高度塌陷问题及其解决方法(BFC相关概念及性质)
浮动引发的高度塌陷问题 高度塌陷问题的产生 BFC(Block Formatting Context)的引入 元素开启BFC后的特点 开启BFC的元素不会被其他浮动元素所覆盖 开启BFC的元素不会发生 ...
- 一文让你对js的原型与原型链不再害怕、迷惑
目录 原型与原型链的详细剖析 原型 显式原型prototype 隐式原型__proto__ 显式原型prototype与隐式原型__proto__的关系 原型链(隐式原型链) 探寻原型链的尽头 完整详 ...
- 面试系列二:精选大数据面试真题JVM专项-附答案详细解析
公众号(五分钟学大数据)已推出大数据面试系列文章-五分钟小面试,此系列文章将会深入研究各大厂笔面试真题,并根据笔面试题扩展相关的知识点,助力大家都能够成功入职大厂! 大数据笔面试系列文章分为两种类型: ...
- ajax请求添加自定义header参数
beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("X-Auth0-Token", g ...
- C# webapi跨域
C# webapi跨域 第一种在Web.config中<system.webServer>节点中配置(不支持多个域名跨域) 1 <httpProtocol> 2 <c ...
- 让人头疼的AI bug (随想)
虽然概念上,人工智能和机器学习不等同.但是本文提及的AI,指的是基于机器学习的AI. 一个软件产品,出了错误叫bug,bug需要修.那一个机器学习的模型,准确率在那摆着呢,大伙心知肚明是有一定的犯 ...
- 使用wireshark 抓取 http https tcp ip 协议进行学习
使用wireshark 抓取 http https tcp ip 协议进行学习 前言 本节使用wireshark工具抓包学习tcp ip http 协议 1. tcp 1.1 tcp三次握手在wire ...