《java.util.concurrent 包源码阅读》19 PriorityBlockingQueue
前面讲ScheduledThreadPoolExecutor曾经重点讲到了DelayedWorkQueue,这里说的PriorityBlockingQueue其实是DelayedWorkQueue的简化版本,实现了按序排列元素的功能。也就是说PriorityBlockingQueue是维护一个按序排列的队列,排序的方法可以通过指定Comparator来比较元素的大小,或者元素类型本身实现了Comparable接口。
因此PriorityBlockingQueue也是使用基于数组的二叉堆来实现的。
首先还是看看offer方法:
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
// 锁定这个队列
lock.lock();
int n, cap;
Object[] array;
// 如果数组已满,则尝试为数组扩容
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
if (cmp == null)
// 没有comparator情况下,元素类型必须实现Comparable接口
// 使用compare方法进行比较,然后插入元素到堆中
siftUpComparable(n, e, array);
else
// 制定comparator的情况下,插入元素使用comparator
// 比较元素,然后插入元素到堆中
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
这里看一下tryGrow,在对数组进行扩容时释放了主锁的,因为分配空间本身是不需要主锁的,只有更新数组时才会要主锁。
这样可以提高并发执行的性能,减少阻塞。
private void tryGrow(Object[] array, int oldCap) {
// 扩容数组时,释放主锁,这样其他取走元素的操作就可以正常
// 操作了。这里使用一个简单的allocationSpinLock作为锁,
// 它的值为1表示锁正在被使用,为0表示锁为被占用。
// 在获取该锁时,用的CAS操作,而释放时,因为锁已经占用,
// 直接赋值为0即可。
// 分配空间本身是用不到主锁的,只有更新数组的时候才需要。
lock.unlock();
Object[] newArray = null;
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) {
try {
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) :
(oldCap >> 1));
if (newCap - MAX_ARRAY_SIZE > 0) {
int minCap = oldCap + 1;
// 整数溢出
if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
throw new OutOfMemoryError();
newCap = MAX_ARRAY_SIZE;
}
// 如果数组被更新了,就没有必要再分配新的空间了
if (newCap > oldCap && queue == array)
newArray = new Object[newCap];
} finally {
allocationSpinLock = 0;
}
}
// 其他线程正在占用allocationSpinLock,调用yield告诉线程调度
// 如果其他线程需要CPU,可以先拿去,我过会再执行,否则我继续执行。
if (newArray == null) // back off if another thread is allocating
Thread.yield();
// 因为要返回,再次获取主锁,而且后面可能要更新数组也需要主锁
lock.lock();
// 如果分配新空间成功,而且原先的队列没有被其他的线程更新过
// 就更新数组。这里不需要使用CAS,因为这个时候已经占用主锁了
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
再看取走元素的核心方法extract(poll方法也是使用这个方法从堆中拿走元素)
private E extract() {
E result;
int n = size - 1;
if (n < 0)
result = null;
else {
// 取走第一个元素
Object[] array = queue;
result = (E) array[0];
E x = (E) array[n];
array[n] = null;
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftDownComparable(0, x, array, n);
else
siftDownUsingComparator(0, x, array, n, cmp);
size = n;
}
return result;
}
这里提一下PriorityBlockingQueue的序列化。PriorityBlockingQueue内置了一个PriorityQueue对象,序列化会把元素转存到这个PriorityQueue中,然后再进行序列化。
反序列化时也是用PriorityQueue读取,然后再把元素转存回PriorityBlockingQueue自己的队列。
private PriorityQueue q;
下一篇会讲具有DelayedWorkQueue的另外一个功能延迟执行的DelayQueue。
《java.util.concurrent 包源码阅读》19 PriorityBlockingQueue的更多相关文章
- 《java.util.concurrent 包源码阅读》 结束语
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
- 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分
这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...
- 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包
Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...
- 《java.util.concurrent 包源码阅读》04 ConcurrentMap
Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...
- 《java.util.concurrent 包源码阅读》17 信号量 Semaphore
学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...
- 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue
对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...
- 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇
concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...
- 《java.util.concurrent 包源码阅读》05 BlockingQueue
想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...
- 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService
AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...
随机推荐
- mybatis的搭建和注入spring的方式
mybatis实际上是一个更多关注sql语句的框架,他的出现是想让开发者更简单的去操作数据库. 与hibernate相比较,hibernate更多的是去sql化,虽然hibernate也可以本地sql ...
- UVa 10954,Add All
Huffman编码简化版,优先队列搞定. 1A 调试的时候发现一个问题..木有想明白...问题代码里给出,哪位大神给解释下. #include <iostream> #include &l ...
- JS中处理单个反斜杠(即转义字符的处理)
问题来源:在表单的<input>标签中对输入的字符串进行大写转换.一不小心输入了反斜杠 \ 如下图所示: 输入 chn\ 的时候,在 IE8 下弹出一个js错误.(在实际的项目的表单 ...
- 对象转字典 iOS
最近在开发SDK,我开放给客户model类设置信息后,对象转字典,POST给后台. 思路:通过Runtime访问属性列表,快速转换成字典. FRObjectToDictionary.h类 p.p1 { ...
- Arduino上传数据至贝壳物联并与FPGA进行交互
本篇实现Arduino与FPGA交互,当然也没有什么新的协议,还是基于串口通讯,现在学一个串口通信基本上可以驱动大多数模块了,而且和各种单片机无缝数据交互,Arduino由于其强大的库函数支持,在实现 ...
- 腾讯云VS AWS :云存储网关性能谁更优?
p { text-indent: 2em } 随着企业规模的扩大及业务的扩展,现有IT基础设施特别是存储设备无法满足爆炸性的数据增长,企业 IT 部门为了解决该问题,往往面临市场上多种存储产品及 ...
- sqoop1.4.6配置安装
1.下载sqoop1.4.6 2.配置环境变量. 3.复制sqoop/conf/sqoop-env-template.sh为sqoop-env.sh 添加相关的配置 #Setpath to where ...
- hdu 3001 Travelling(状态压缩 三进制)
Travelling Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- Codeforces 376A. Night at the Museum
A. Night at the Museum time limit per test 1 second memory limit per test 256 megabytes input standa ...
- 附录三 关于book.h
本书中用到的公用函数放到了头文件book.h中. #ifndef __BOOK_H__ #define __BOOK_H__ #include <stdio.h> #include < ...