JUC阻塞队列BlockingQueue---DelayQueue

什么是阻塞队列?

DelayQueue

DelayQueue 是一个支持延时获取元素的阻塞无界队列, 内部采用优先队列 PriorityQueue 存储元素,同时元素必须实现 Delayed 接口。在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。

延迟队列的特点:不是先进先出,而是会按照延迟时间的长短来排序,下一个即将执行的任务会排到队列的最前面。

Delayed接口

Delayed 接口又继承了 Comparable 接口,所以自然就拥有了比较和排序的能力。

public interface Delayed extends Comparable<Delayed> {

    /**
     * Returns the remaining delay associated with this object, in the
     * given time unit.
     *
     * @param unit the time unit
     * @return the remaining delay; zero or negative values indicate
     * that the delay has already elapsed
     */
    long getDelay(TimeUnit unit);
}

使用

实现延迟订单

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayQueueExample {

    public static void main(String[] args) throws InterruptedException {
        DelayQueue<Order> delayQueue = new DelayQueue<>();

        // 添加三个订单,分别延迟 5 秒、2 秒和 3 秒
        delayQueue.put(new Order("order1", System.currentTimeMillis(), 5000));
        delayQueue.put(new Order("order2", System.currentTimeMillis(), 2000));
        delayQueue.put(new Order("order3", System.currentTimeMillis(), 3000));

        // 循环取出订单,直到所有订单都被处理完毕
        while (!delayQueue.isEmpty()) {
            Order order = delayQueue.take();
            System.out.println("处理订单:" + order.getOrderId());
        }
    }

    static class Order implements Delayed {
        private String orderId;
        private long createTime;
        private long delayTime;

        public Order(String orderId, long createTime, long delayTime) {
            this.orderId = orderId;
            this.createTime = createTime;
            this.delayTime = delayTime;
        }

        public String getOrderId() {
            return orderId;
        }

        /**
         * 计算订单的剩余延迟时间
         *
         * @param unit the time unit
         * @return
         */
        @Override
        public long getDelay(TimeUnit unit) {
            long diff = createTime + delayTime - System.currentTimeMillis();
            return unit.convert(diff, TimeUnit.MILLISECONDS);
        }

        /**
         * 根据订单的延迟时间进行比较
         *
         * @param o the object to be compared.
         * @return
         */
        @Override
        public int compareTo(Delayed o) {
            long diff = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
            return Long.compare(diff, 0);
        }
    }
}

原理

构造方法

    /**
     * Creates a new {@code DelayQueue} that is initially empty.
     */
    public DelayQueue() {}

    /**
     * Creates a {@code DelayQueue} initially containing the elements of the
     * given collection of {@link Delayed} instances.
     *
     * @param c the collection of elements to initially contain
     * @throws NullPointerException if the specified collection or any
     *         of its elements are null
     */
    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }

    /**
     * Adds all of the elements in the specified collection to this
     * queue.  Attempts to addAll of a queue to itself result in
     * <tt>IllegalArgumentException</tt>. Further, the behavior of
     * this operation is undefined if the specified collection is
     * modified while the operation is in progress.
     *
     * <p>This implementation iterates over the specified collection,
     * and adds each element returned by the iterator to this
     * queue, in turn.  A runtime exception encountered while
     * trying to add an element (including, in particular, a
     * <tt>null</tt> element) may result in only some of the elements
     * having been successfully added when the associated exception is
     * thrown.
     *
     * @param c collection containing elements to be added to this queue
     * @return <tt>true</tt> if this queue changed as a result of the call
     * @throws ClassCastException if the class of an element of the specified
     *         collection prevents it from being added to this queue
     * @throws NullPointerException if the specified collection contains a
     *         null element and this queue does not permit null elements,
     *         or if the specified collection is null
     * @throws IllegalArgumentException if some property of an element of the
     *         specified collection prevents it from being added to this
     *         queue, or if the specified collection is this queue
     * @throws IllegalStateException if not all the elements can be added at
     *         this time due to insertion restrictions
     * @see #add(Object)
     */
    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;
    }

常量


	//用于保证队列操作的线程安全
    private final transient ReentrantLock lock = new ReentrantLock();

	// 优先级队列,存储元素,用于保证延迟低的优先执行
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    /**
     * Thread designated to wait for the element at the head of
     * the queue.  This variant of the Leader-Follower pattern
     * (http://www.cs.wustl.edu/~schmidt/POSA/POSA2/) serves to
     * minimize unnecessary timed waiting.  When a thread becomes
     * the leader, it waits only for the next delay to elapse, but
     * other threads await indefinitely.  The leader thread must
     * signal some other thread before returning from take() or
     * poll(...), unless some other thread becomes leader in the
     * interim.  Whenever the head of the queue is replaced with
     * an element with an earlier expiration time, the leader
     * field is invalidated by being reset to null, and some
     * waiting thread, but not necessarily the current leader, is
     * signalled.  So waiting threads must be prepared to acquire
     * and lose leadership while waiting.
     */
    // 用于标记当前是否有线程在排队(仅用于取元素时) leader 指向的是第一个从队列获取元素阻塞的线程
    private Thread leader = null;

    /**
     * Condition signalled when a newer element becomes available
     * at the head of the queue or a new thread may need to
     * become leader.
     */
    // 条件,用于表示现在是否有可取的元素   当新元素到达,或新线程可能需要成为leader时被通知 
    private final Condition available = lock.newCondition();

入队put方法

    /**
     * Inserts the specified element into this delay queue. As the queue is
     * unbounded this method will never block.
     *
     * @param e the element to add
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) {
        offer(e);
    }
    
    /**
     * Inserts the specified element into this delay queue.
     *
     * @param e the element to add
     * @return {@code true}
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
    	// 加锁
        lock.lock();
        try {
        	
        	// 入队
            q.offer(e);
            
            if (q.peek() == e) {
    	        // 若入队的元素位于队列头部,说明当前元素延迟最小
	            // 将 leader 置空
                leader = null;
                
	            // available条件队列转同步队列,准备唤醒阻塞在available上的线程
                available.signal();
            }
            return true;
        } finally {
	        // 解锁,真正唤醒阻塞的线程
            lock.unlock();
        }
    }

出队take方法

    /**
     * Retrieves and removes the head of this queue, waiting if necessary
     * until an element with an expired delay is available on this queue.
     *
     * @return the head of this queue
     * @throws InterruptedException {@inheritDoc}
     */
    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 <= 0)
                    	// 如果小于0说明已到期,直接调用poll()方法弹出堆顶元素
                        return q.poll();
                    
                    // 如果delay大于0 ,则下面要阻塞了
                    // 将first置为空方便gc    
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        available.await();
                    else {
                    	// 如果leader为null,把当前线程赋值给它
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                        	// 等待剩余等待时间
                            available.awaitNanos(delay);
                        } finally {
                        	// 如果leader还是当前线程就把它置为空,让其它线程有机会获取元素
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
        	// 成功出队后,如果leader为空且堆顶还有元素,就唤醒下一个等待的线程
            if (leader == null && q.peek() != null)
            	// available条件队列转同步队列,准备唤醒阻塞在available上的线程
                available.signal();
                
            // 解锁,真正唤醒阻塞的线程
            lock.unlock();
        }
    }

步骤总结

  1. 当获取元素时,先获取到锁对象。
  2. 获取最早过期的元素,但是并不从队列中弹出元素。
  3. 最早过期元素是否为空,如果为空则直接让当前线程无限期等待状态,并且让出当前锁对象。
  4. 如果最早过期的元素不为空
  5. 获取最早过期元素的剩余过期时间,如果已经过期则直接返回当前元素
  6. 如果没有过期,也就是说剩余时间还存在,则先获取Leader对象,如果Leader已经有线程在处理,则当前线程进行无限期等待,如果Leader为空,则首先将Leader设置为当前线程,并且让当前线程等待剩余时间。
  7. 最后将Leader线程设置为空
  8. 如果Leader已经为空,并且队列有内容则唤醒一个等待的队列。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值