面试|详细理解优先队列DelayedWorkQueue

DelayedWorkQueue优先队列

该队列是定制的优先级队列,只能用来存储RunnableScheduledFutures任务。堆是实现优先级队列的最佳选择,而该队列正好是基于堆数据结构的实现。

1.关于堆的一些知识

堆结构是用数组实现的二叉树,数组下标可以表明元素节点的位置,所以省去指针的内存消耗;堆内元素节点的位置取决于节点的某一个属性的大小值,根据父节点是否大于左右节点分为最小堆和最大堆。即二叉树根节点最小则为最小堆,二叉树根节点最大则为最大堆;下面是最小堆和最大堆的示例:
最小堆
最小堆中,父节点都小于左右节点;其数组形式:[1, 5, 8, 6, 10, 11, 20]
最大堆
最大堆总,父节点都大于左右节点;其数组形式:[20, 10, 15, 6, 9, 10, 12]

2.堆的一些属性
  • 堆都是满二叉树.因为满二叉树会充分利用数组的内存空间;
  • 最小堆是指父节点比左节点和右节点都小的结构,所以整个最小堆中,根节点是最小的节点;
  • 最大堆是指父节点比左节点和右节点都大的结构,所以整个最大堆中,根节点是最大的节点;
  • 最大堆和最小堆的左节点和右节点没有关系,只能判断父节点和左右两节点的大小关系;

基于堆的这些属性,堆适用于找到集合中的最大或者最小值;另外,堆结构记录任务及其索引的关系,便于插入数据或者删除数据后重新排序,所以堆适用于优先队列。

3.堆和数组

堆结构是二叉树,节点实际存储在数组中。如果i是节点的索引,那么就可以用下面的公式计算父节点和子节点在数组中的位置:

// 父节点
parent(i) =floor( (i – 1) / 2);
// 左节点
left(i) = 2 * i + 1;
// 右节点
right(i) = 2 * i + 2;

左节点和右节点在数组中永远是相邻的.例如上面的最小堆,数组为:[1, 5, 8, 6, 10, 11, 20],数组中的元素在满二叉树中就是从上到下从左到右分布着。
最小堆1

节点属性值 节点索引i 节点父节点 floor( (i – 1) / 2) 节点左节点2 * i + 1 节点右节点2 * i + 2
1 0 -1 1 2
5 1 0 3 4
8 2 0 5 6
6 3 1 - -
10 4 1 - -
11 5 2 - -
20 6 2 - -
4.堆的插入和移除

堆元素的插入siftUp操作和移除siftDown操作。
比如在最小堆[1, 5, 8, 6, 10, 11, 20]中再插入一个元素4,下面用图示分析插入的过程:
插入之前的二叉树结构:
插入1
插入元素4首先放置在数组末尾,也就是二叉树的末尾:
插入2
插入元素4(索引为7),首先与其父类(元素为6,索引为3)比较大小,如果是最小堆则交换其与父类的位置,如果是最大堆则不用交换直接结束。我们这里是最小堆,4比6小,所以需要交换其与父类的位置:
插入3
交换后如上图所示,找到此时元素4(索引为3)的父类(元素为5,索引为1),比较两者的元素大小,4小于5,所以交换两者的位置:
插入4
交换后如上图所示,找到此时元素4(索引为1)的父类(元素为1,索引为0),比较两者的元素大小,4大于1,所以不需要交换。此时确定插入元素4的索引为1的位置。插入的二叉树如图所示:
插入5
插入后最小堆的数组形式:[1,4,8,5,10,11,20,6]

整个插入的过程就是一个不断循环比较的过程,通过比较插入元素与父节点元素的大小,最小堆则把较小的元素放置在父节点处,最大堆则把较大的元素放置在父节点处,直到确认插入元素的节点位置。可以看出,每插入一个元素都会对堆进行重排序,且每次排序都是二分排序,所以时间复杂度为logn。

在最大堆[20, 10, 15, 6, 9, 10, 12]中移除元素后,下面用图示分析重排的过程:
最大堆原始二叉树结构:
移除1
移除堆的根节点(元素值为20,索引为0)后,取二叉树的最后一个节点,即数组中的最后一个元素12(二叉树中索引为6)放入根节点位置:
移除2
需要对新二叉树进行重排序;首先获取根节点的左、右节点,然后比较其大小(最大堆找到较大的节点,最小堆找到较小的节点),我们这里是最大堆,所以比较左节点10和右节点15的大小,找到较大的右节点,并比较右节点(元素为15,索引为2)与根节点(元素为12,索引为0)的大小,把较大的元素放入根节点(最小堆是把较小的元素放入根节点),交换后的二叉树图为:
移除3
重复上面的步骤,比较元素12(索引为2)与其左节点(元素为10,索引为5)的大小,把较大的元素放置在父节点上。最后的二叉树图为:
移除4
移除根节点后最大堆的数组形式:[15,10,12,6,9,10]

从堆中获取节点都是默认获取根节点,因为根节点是整个队列中最大或者最小的元素。获取后,需要对队列重排寻找新的根节点元素。重排的顺序跟新增后重排的顺序相反,它是从根节点往下重排,最大堆则把较大元素放置在父节点上,最小堆则把较小元素放置在父节点上;它的时间复杂度同样为logn。

5.DelayedWorkQueue

DelayedWorkQueue是基于堆结构的等待队列。
DelayedWorkQueue

5.1 类的重要属性
// 数组的初始容量为16 设置为16的原因跟hashmap中数组容量为16的原因一样
private static final int INITIAL_CAPACITY = 16;
// 用于记录RunableScheduledFuture任务的数组
private RunnableScheduledFuture<?>[] queue =
            new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final ReentrantLock lock = new ReentrantLock();
// 当前队列中任务数,即队列深度
private int size = 0;
// leader线程用于等待队列头部任务,
private Thread leader = null;
// 当线程成为leader时,通知其他线程等待
private final Condition available = lock.newCondition();

延迟队列是基于数组实现的,初始容量为16;获取延迟队列中头部节点的线程称为leader,说明leader线程是不断变化的,但leader线程在等待,则其他线程也会等待,直到leader线程获取根节点,且从等待线程中产生新的leader线程。

5.2 新增元素到DelayedWorkQueue
1) offer(Runnable)-新增元素

给外界提供一个插入元素的方法.

public boolean offer(Runnable x) {
   
    if (x == null)
        throw new NullPointerException();
	// 只能存放RunnableScheduledFuture任务
    RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
    // 为了保证队列的线程安全,offer()方法为线程安全方法
	final ReentrantLock lock = this.lock;
    lock.lock();
	try {
   
  • 17
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值