DelayQueue

DelayQueue阅读笔记

一、简介

​ 元素最大容量没有限制,内部采用PriorityQueue优先队列存储元素,添加和获取元素都进行了ReentrantLock加锁,同时集合中没有元素时进行等待采用的Condition条件锁。所有操作都加入了ReentrantLock锁

二、继承关系图

在这里插入图片描述

三、存储结构

​ PriorityQueue优先队列

四、源码分析

属性
/** 用于控制并发的锁 */
private final transient ReentrantLock lock = new ReentrantLock();
/** 存储元素集合:优先级队列 */
private final PriorityQueue<E> q = new PriorityQueue<E>();
/** 用于标记当前是否有线程在排队(仅用于取元素时) */ 
private Thread leader = null;
/** 可用条件锁:当集合为null就awrit,当添加元素则signal 。用于表示现在是否有可取的元素*/
private final Condition available = lock.newCondition();
构造
/** 默认无参构造 */
public DelayQueue() {}

/** 构造构造,可设定初始化的加入的元素集合,不可为null */
public DelayQueue(Collection<? extends E> c) {
    // 调用的是父类 AbstractQueue 的addAll方法
    this.addAll(c);
}

/** AbstractQueue.addAll(Collection<? extends E> c) */
public boolean addAll(Collection<? extends E> c) {
    if (c == null)
        throw new NullPointerException();
    if (c == this)
        throw new IllegalArgumentException();
    boolean modified = false;//默认添加失败
    // 循环取出集合元素加入到当前集合
    for (E e : c)
        if (add(e))
            // 如果添加成功则修改添加成功
            modified = true;
    return modified;
}
主要方法
入队操作
  • add、put、offer延时 都是直接调用的offer,没做任何变化
public boolean add(E e) {
    return offer(e);
}
public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 调用优先队列PriorityQueued.offer方法进行元素元素添加
        q.offer(e);
        if (q.peek() == e) {
            // 判断添加的元素是否是优先头节点
            // 如果是就直接设置leader为null,且调用available条件锁唤醒wait中的等待线程
            leader = null;
            available.signal();
        }
        // 返回添加成功
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}
public void put(E e) {
    offer(e);
}
public boolean offer(E e, long timeout, TimeUnit unit) {
    return offer(e);
}
出队操作
  • remove() :删除元素,并且返回被删除的元素,如果为null或过期抛出异常
  • poll():删除元素,并且返回被删除的元素,如果为null或过期返回null,
  • task():
    • 判断堆顶元素是否为null、为null就直接阻塞队列,
    • 判断堆顶元素是否到期,到期了直接调用PriorityQueue.poll进行弹出元素,成功返回旧值,
  • poll(long timeout, TimeUnit unit):直接获取元素,如果在指定时间没有获取元素,直接返回null
/** 父类:AbstractQueue.remove方法 */
public E remove() {
    // 调用自身的polll()函数
    E x = poll();
    if (x != null)
        // 如果元素不为null 返回被删除的元素
        return x;
    else
        // 元素为null,抛出NoSuchElementException异常
        throw new NoSuchElementException();
}   

/** 取出队列元素,并返回,如果不存在返回null */
public E poll() {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获取优先队列第一个元素
        E first = q.peek();
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            // 如果第一个元素为null
            // 或者第一个元素到期了
            // 返回null
            return null;
        else
            // 返回第一个元素,并从队列移除
            return q.poll();
    } finally {
        // 释放锁
        lock.unlock();
    }
}

/** 取元素,如果没有元素,则加入条件等待线程队列*/
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 加锁,如果线程中断抛出异常
    lock.lockInterruptibly();
    try {
        // 自旋锁
        for (;;) {
            // 堆顶元素
            E first = q.peek();
            if (first == null)
                // 如果堆顶元素为null,说明队列中没有元素,则加入到条件锁的等待线程队列
                available.await();
            else {
                // 堆顶元素的剩余延迟,延迟只有低于0及以下才可以获取元素
                long delay = first.getDelay(NANOSECONDS);
                // 如果小于0 说明已到期,直接调用poll()弹出栈顶元素
                if (delay <= 0) 
                    return q.poll();
                // 程序走到这里,说明第一个元素已经超时,
                // 所以直接设置为null,便于gc,因为有可能其他线程弹出了元素
                first = null; // don't retain ref while waiting
                if (leader != null)
                    // 如果leader线程不是null,
                    // 说明有其他线程在等待,则直接线程等待
                    available.await();
                else {
                    // 如果leader线程为null,
                    // 把当前线程设置为leader线程
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        // 等待delay时候后自动醒来
                        // 醒来后把leader置为nulll并重新加入循环判断顿对元素是否到期
                        // 这里既使醒过来后也不一定能获取到元素
                        // 因为有可能其他线程先一步获取了并弹出了堆顶元素
                        // 条件锁的唤醒分成两部,先从condition的队列中出队
                        // 再入队到AQS的队列中,当其他线程抵用LockSupport.unpark(t)的时候
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            // 如果当前线程还leader,就把lead设置我null,
                            // 因为相等说明我已经取过元素了,可以退出了
                            // 让其他线程有机会获取元素
                            leader = null;
                    }
                }
            }
        }
    } finally {
        /// 成功出队后,如果leader为null且堆顶还有元素且堆顶元素,就唤醒下一个等待的线程
        if (leader == null && q.peek() != null)
            // 只是把等待的线程放在AQS的队列李雷,并不是真正的唤醒
            available.signal();
        // 释放锁
        lock.unlock();
    }
}

/** 取元素,如果没有元素就等待timout时间,然后再重新获取,如果还是timeout时间内还没有获取就返回null */
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 加锁。如果线程中断抛出异常
    lock.lockInterruptibly();
    try {
        for (;;) {
            // 取出队列头元素
            E first = q.peek();
            if (first == null) {
                // 如果元素为null进入
                if (nanos <= 0)
                    // 等待时间已过,直接返回null
                    return null;
                else
                    // 直接等待纳秒时间,然后重新循环
                    nanos = available.awaitNanos(nanos);
            } else {
                // 如果不为null,就获取元素的剩余延迟,也就是延迟时间没过,不能获取元素
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0)
                    // 延迟时间已过去。直接取出元素
                    return q.poll();
                if (nanos <= 0)
                    // 等待时间已过直接返回null
                    return null;
                first = null; // don't retain ref while waiting   帮忙gc
                if (nanos < delay || leader != null)
                    // 如果等待时间小于延迟时间,那么就直接加入条件等待队列等待,
                    // 如果leader线程有等待,那么就直接加入条件等待队列等待,
                    // 因为还有其他线程需要获取元素嘛
                    nanos = available.awaitNanos(nanos);
                else {
                    // 进入这里说明leader == null,直接把当前线程赋值给thisThread
                    Thread thisThread = Thread.currentThread();
                    // 直接把当前线程赋值给leader,相当于直接占用等待时间,反正是null嘛
                    leader = thisThread;
                    try {
                        // 等待指定延迟时间(进入这里说明超时时间大于等于延迟时间)
                        // 等待后的剩余时间,
                        long timeLeft = available.awaitNanos(delay);
                        // 重新计算nanos时间
                        nanos -= delay - timeLeft;
                    } finally {
                        if (leader == thisThread)
                            // 如果他们一样,就释放释放leader,
                            // 然后重新循环获取元素
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null)
            // 元素获取成功后,也可能获取的是null,超时没有获取
            // 如果没有线程在等待,且优先队列不等于null
            // 那么就唤醒处于available.await等待的线程来获取元素
            available.signal();
        lock.unlock();
    }
}
获取队列顶元素
public E peek() {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        return q.peek();
    } finally {
        // 释放锁
        lock.unlock();
    }
}
删除元素
public boolean remove(Object o) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        return q.remove(o);
    } finally {
        // 释放锁
        lock.unlock();
    }
}
补充
使用demo
public class DelayTest {

    public static void main(String[] args) {
        DelayQueue<Message> queue = new DelayQueue<>();
        long now = System.currentTimeMillis();

        new Thread(() -> {
            while (true) {
                try {
                    // 1000.2000.5000。7000.8000
                    System.out.println(queue.take().deadline - now);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        queue.add(new Message(now + 5000));
        queue.add(new Message(now + 8000));
        queue.add(new Message(now + 2000));
        queue.add(new Message(now + 1000));
        queue.add(new Message(now + 7000));
    }
}

class Message implements Delayed {
    long deadline;

    public Message(long deadline) {
        this.deadline = deadline;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return deadline - System.currentTimeMillis();
    }

    @Override
    public int compareTo(Delayed o) {
        return (int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }

    @Override
    public String toString() {
        return String.valueOf(deadline);
    }
}

五、总结

  • 阻塞对垒
  • 内部存储结构使用优先级队列
  • 使用个重入锁和条件锁来控制并发安全
  • 常用语定时任务
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值