并发编程--延迟队列

1 DelayQueue

使用场景:让消息具有延迟消费的特性

也是基于二叉堆结构实现的,不同之处是DelayQueue是将剩余延迟时间最短的放在堆顶,继承Delayed接口,因此在使用DelayQueue时,需要实现Delayed方法,并重写两个方法。

DelayQueue底层使用了PriorityQueue,所以DelayQueue也是无界队列

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {
}
public interface Delayed extends Comparable<Delayed> {

    long getDelay(TimeUnit unit);
}

public class TestBlockingQueue implements Delayed {
    @Override
    public long getDelay(TimeUnit unit) {
        return 0;
    }

    @Override
    public int compareTo(Delayed o) {
        return 0;
    }
}

在应用上,比如外卖15分钟商家需要接单,否则,订单自动取消,可以每下一个订单就放在延迟队列中,如果规定时间内,商家没有接单,则通过消费者获取元素,然后取消订单。

核心属性分析

// 因为DelayQueue依然是阻塞队列,需要保证线程安全
private final transient ReentrantLock lock = new ReentrantLock();

// 底层使用PriorityQueue实现,底层依然是二叉堆结构
private final PriorityQueue<E> q = new PriorityQueue<E>();        

// 存储等待栈顶数据的消费者
private Thread leader;

// 生产者插入数据时,不会阻塞
private final Condition available = lock.newCondition();

2 DelayQueue的写入源码分析

public boolean add(E e) {
    return offer(e);
}
public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //调用PriorityQueue的offer方法,会根据传入的排序方式,进行节点的上移下移操作
        q.offer(e);
        // 调用PriorityQueue的peek方法,如果栈顶的元素等于当前元素,则设置leader为空,并唤醒消费者线程
        if (q.peek() == e) {
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        lock.unlock();
    }
}

3 DelayQueue的读取源码分析

消费者存在阻塞的情况:

  • 消费者要拿到栈顶的数据,但是延迟时间还没到,此时需要等待一会儿

  • 消费者要来拿数据,但是发现已经有消费者在等待栈顶数据了,这个后来的消费者也需要等待一会儿

3.1 remove方法实现原理分析

public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

3.2 poll方法实现原理分析

// poll是浅尝一下,能拿到就返回,拿不到就拉倒
public E poll() {
    // 消费者与生产者是一把锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 拿到栈顶数据
        E first = q.peek();
        // 如果元素为null ,直接返回null;如果数据的延迟时间>0,说明还没到延迟时间,元素无法返回,返回null
        return (first == null || first.getDelay(NANOSECONDS) > 0)
            ? null
            // 到达了延迟时间,此时调用优先级队列的poll方法返回数据
            : q.poll();
    } finally {
        lock.unlock();
    }
}

3.3 poll(long timeout, TimeUnit unit)实现原理分析

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    // 转为ns更精确
    long nanos = unit.toNanos(timeout);
    // 拿锁
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lockInterruptibly();
    try {
        for (;;) {
            // 获取栈顶数据
            E first = q.peek();
            if (first == null) {
                if (nanos <= 0L)
                    // 栈顶为null,并且没有时间了,直接返回null
                    return null;
                else
                    // 栈顶为null,但还有时间,此时调用Condition的挂起nanos时间的线程操作,挂起线程后,在生产者添加数据后唤醒
                    nanos = available.awaitNanos(nanos);
            } else {
                // 获取栈顶元素的延迟时间
                long delay = first.getDelay(NANOSECONDS);
                // 看当前数据的延迟时间是否到了,到了则直接执行优先级队列的poll方法,返回元素
                if (delay <= 0L)
                    return q.poll();
                //延迟时间未到,消费者需要等待
                // 消费者可以等待的时间,如果到了,直接返回null
                if (nanos <= 0L)
                    return null;
                // first赋值为null
                first = null; // don't retain ref while waiting
                // 等待的时间小于剩余的延迟时间,消费者直接挂起,暂时无法拿到元素
                // 如果leader不为null,说明有消费者在等待栈顶数据,直接挂起即可
                if (nanos < delay || leader != null)
                    nanos = available.awaitNanos(nanos);
                else {
                    // 当前消费者的阻塞时间可以拿到数据,并且没有其他消费者在等待栈顶数据
                    // 拿到当前消费者的线程对象
                    Thread thisThread = Thread.currentThread();
                    // 设置leader为当前线程
                    leader = thisThread;
                    try {
                        // 让当前消费者阻塞这个元素的延迟时间
                        long timeLeft = available.awaitNanos(delay);
                        // 重新计算当前消费者剩余的可阻塞时间
                        nanos -= delay - timeLeft;
                    } finally {
                        //当前线程设置leader为null
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        // 最后如果leader为null 并且有限队列取出的栈顶数据不为空,则唤醒线程
        if (leader == null && q.peek() != null)
            //需要再次唤醒后继消费者,避免队列有元素,但没有消费者唤醒的问题
            available.signal();
        lock.unlock();
    }
}

3.4 take方法的原理分析

要么等到元素,要么等到被中断

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 可被中断的锁
    lock.lockInterruptibly();
    try {
        for (;;) {
            E first = q.peek();
            if (first == null)
                // 没有元素直接挂起
                available.await();
            else {
                // 有元素,获取延迟时间
                long delay = first.getDelay(NANOSECONDS);

                if (delay <= 0L)
                    // 延迟时间小于等于0,直接调用优先级队列的poll方法,返回元素
                    return q.poll();
                // 设置元素为null
                first = null; // don't retain ref while waiting
                if (leader != null)
                    // leader不等于空,说明有消费者在等待元素,此时调用Condition的等待线程挂起的方法
                    available.await();
                else {
                    // 获取当前线程
                    Thread thisThread = Thread.currentThread();
                    // 设置当前线程为消费者,赋值给leader
                    leader = thisThread;
                    try {
                        // 调用Condition的等待线程挂起延迟时间的方法
                        available.awaitNanos(delay);
                    } finally {
                        // 当前消费者线程重置leader为null
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {

        // 最后如果leader为null 并且有限队列取出的栈顶数据不为空,则唤醒线程
        if (leader == null && q.peek() != null)
            //需要再次唤醒后继消费者,避免队列有元素,但没有消费者唤醒的问题
             available.signal();
        lock.unlock();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值