java集合之DelayedWorkQueue

DelayedWorkQueue 是一个定制的阻塞队列,专门用来存放RunnableScheduledFutures任务,它是有界的,有最大容量,一般达不到,虽然它是队列,它并不能满足先进先出规则,它是按优先级出队,它会将插入的数据进行排序,按优先级出队,底层采用数组实现的二叉堆,它是为定时线程池服务的,它会根据提交进来的任务的延迟时间进行排序,始终将距离当前时间最近的任务排在前面。具体情况,我们根据源码来解释
在这里插入图片描述

1、重要属性
// 队列初始容量
private static final int INITIAL_CAPACITY = 16;
// 存放任务的数组
private RunnableScheduledFuture<?>[] queue =
    new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
// 锁
private final ReentrantLock lock = new ReentrantLock();
// 元素个数
private int size = 0;
// leader一般会存储等待堆顶数据的消费者
private Thread leader = null;
// 等待队列
private final Condition available = lock.newCondition();
2、重要方法
2.1 写入方法
2.1.1 add方法
public boolean add(Runnable e) {
    return offer(e);
}
2.1.2 put方法
public void put(Runnable e) {
  offer(e);
}
2.1.3 offer方法
// offer方法向队列中添加元素
// e 需要添加到队列的数据
// timeout 等待时间
// unit 时间单位
public boolean offer(Runnable e, long timeout, TimeUnit unit) {
  return offer(e);
}
public boolean offer(Runnable x) {
	if (x == null)
	    throw new NullPointerException();
	// 强转
	RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
	// 加锁
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		// 将size赋值给i,size是当前队列中的元素个数
	    int i = size;
	    // 队列满了,就进行扩容
	    if (i >= queue.length)
	        grow();
        // size+1
	    size = i + 1;
	    // 如果队列中没有元素,默认放在第0个位置
	    if (i == 0) {
	        queue[0] = e;
	        setIndex(e, 0);
	    } else {
	    	// 通过siftUp,比较数据大小,存储数据,并根据优先级排序规则进行移动,保证二叉树平衡
	        siftUp(i, e);
	    }
	    // 队列头部元素变成了当前的e,则需要将leader设置为null,因为leader等待的是之前的队头数据
	    // 唤醒等待队列中的消费者,来等待当前的队头数据
	    if (queue[0] == e) {
	        leader = null;
	        available.signal();
	    }
	} finally {
		// 释放锁
	    lock.unlock();
	}
	return true;
}
// 扩容
private void grow() {
	// 数组当前容量
    int oldCapacity = queue.length;
    // 扩容到原数组的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
    // newCapacity变为负数,长度已经大于MAX_VALUE了,则取MAX_VALUE。
    if (newCapacity < 0) // overflow
        newCapacity = Integer.MAX_VALUE;
    // 将当前数组拷贝到新数组
    queue = Arrays.copyOf(queue, newCapacity);
}

// 存储数据,并根据优先级排序规则进行移动,保证二叉树平衡 
// k是当前key要存放的位置,也就是队尾,但是要保证最小堆原值,还需要进行调整
private void siftUp(int k, RunnableScheduledFuture<?> key) {
   while (k > 0) {
   		// 找到父节点的位置
        int parent = (k - 1) >>> 1;
        // 取出父元素
        RunnableScheduledFuture<?> e = queue[parent];
        // 如果当前加入队列的元素比父节点大,则直接放
        if (key.compareTo(e) >= 0)
            break;
        // 如果加入的元素比父节点小,则将父节点放到k位置,也就是父节点往下移动
        queue[k] = e;
        setIndex(e, k);
        // 重新设置x节点需要放置的位置。k就来到了父节点原本的位置,此时再进行while判断,
        // 如果合适则放下,不合适继续重复刚才的操作,直到找到合适的位置,或者找到根节点
        k = parent;
    }
    // 将key放在找到的k位置
    queue[k] = key;
    setIndex(key, k);
}

2.2 读取操作
2.2.1 peek方法

查看堆顶元素

public RunnableScheduledFuture<?> peek() {
  final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 直接返回堆顶元素
        return queue[0];
    } finally {
        lock.unlock();
    }
}
2.2.2 poll方法

不阻塞的poll,取出堆顶元素,如果堆顶元素还没到出队时间或者堆顶元素为空,直接返回null,否则返回堆顶元素
阻塞的poll,等待阻塞时间,指定时间内拿到元素返回,拿不到元素,时间到了返回null

public RunnableScheduledFuture<?> poll() {
    // 加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	// 获取堆顶元素
        RunnableScheduledFuture<?> first = queue[0];
        // 堆顶元素为空或者还没到出队时间 返回null
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
        	// 将最后一个元素放到堆顶,并向下堆化(最后一个元素的延迟时间肯定是最大的,
        	// 将它放到堆顶之后,需要重新给他找位置)
            return finishPoll(first);
    } finally {
    	// 释放锁
        lock.unlock();
    }
}
private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
	// 元素个数减1
   int s = --size;
   // 取出最后一个元素
    RunnableScheduledFuture<?> x = queue[s];
    // 将最后一个位置置为null
    queue[s] = null;
    // 如果s== 0 说明已经是最后一个元素,不需要保证堆结构
    if (s != 0)
    	// 由于堆顶元素出队列后,就破坏了堆的结构,需要组织整理,将堆尾元素移到堆顶,然后向下堆化
        siftDown(0, x);
    // 出堆后,将index置为-1
    setIndex(f, -1);
    return f;
}
// key堆底元素
// k堆底元素放置的位置(默认为0)
private void siftDown(int k, RunnableScheduledFuture<?> key) {
	// 无符号右移,相当于size/2
	// // 因为二叉堆是一个二叉满树,所以在保证二叉堆结构时,只需要做一半就可以
    int half = size >>> 1;
    // 做了超过一半,就不需要再往下找了。
    while (k < half) {
    	// 寻找子节点
        int child = (k << 1) + 1;
        // 左子节点
        RunnableScheduledFuture<?> c = queue[child];
        // 又子节点的位置
        int right = child + 1;
        // right < size 保证右子节点存在
        // c.compareTo(queue[right]) > 0 左子节点比右子节点大
        if (right < size && c.compareTo(queue[right]) > 0)
        	// 取出较小的子节点
            c = queue[child = right];
        // 如果堆尾元素比当前较小的子节点小 则不用继续寻找,跳出循环
        if (key.compareTo(c) <= 0)
            break;
        // 否则将较小的子节点c向上提,放到堆顶
        queue[k] = c;
        setIndex(c, k);
        k = child;
    }
    // 最终将key放到找到的k位置
    queue[k] = key;
    setIndex(key, k);
}

public RunnableScheduledFuture<?> poll(long timeout, TimeUnit unit)
  throws InterruptedException {
   long nanos = unit.toNanos(timeout);
   final ReentrantLock lock = this.lock;
   lock.lockInterruptibly();
   try {
       for (;;) {
           RunnableScheduledFuture<?> first = queue[0];
           if (first == null) {
               if (nanos <= 0)
                   return null;
               else
                   nanos = available.awaitNanos(nanos);
           } else {
               long delay = first.getDelay(NANOSECONDS);
               if (delay <= 0)
                   return finishPoll(first);
               if (nanos <= 0)
                   return null;
               first = null; // don't retain ref while waiting
               if (nanos < delay || leader != null)
                   nanos = available.awaitNanos(nanos);
               else {
                   Thread thisThread = Thread.currentThread();
                   leader = thisThread;
                   try {
                       long timeLeft = available.awaitNanos(delay);
                       nanos -= delay - timeLeft;
                   } finally {
                       if (leader == thisThread)
                           leader = null;
                   }
               }
           }
       }
   } finally {
       if (leader == null && queue[0] != null)
           available.signal();
       lock.unlock();
   }
}
2.2.3 take方法

如果取不到元素就死等,直到取到元素为止

public RunnableScheduledFuture<?> take() throws InterruptedException {
	// 加锁
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
        	// 堆顶元素为空,当前读线程阻塞
            RunnableScheduledFuture<?> first = queue[0];
            if (first == null)
                available.await();
            else {
            	// 取出堆顶元素的出堆时间
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0)
                	// 时间已到 执行出堆
                    return finishPoll(first);
                // 将first置空,线程等待时,不持有堆顶元素的引用(当线程醒来时,堆顶元素可能已经变化)
                first = null; // don't retain ref while waiting
                // leader为当前正在等待堆顶元素的线程,如果已经有人在等堆顶元素,那么当前线程阻塞
                if (leader != null)
                    available.await();
                else {
                	// 走到这里,说明当前消费者的阻塞时间可以拿到数据,并且没有其他消费者在等待堆顶数据
                	// 将leader设置为当前线程
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                    	// 阻塞任务的延迟时间 当任务的延时时间到了时,能够自动超时唤醒
                        available.awaitNanos(delay);
                    } finally {
                    	// 将如果leader是当前线程,置为空
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
    	// 没有消费者在等待元素,队列中的元素不为null 
        if (leader == null && queue[0] != null)
        	// 只要当前没有leader在等,并且队列有元素,就需要再次唤醒消费者。、
            // 避免队列有元素,但是没有消费者处理的问题
            available.signal();
        // 释放锁
        lock.unlock();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值