java集合之DelayQueue

DelayQueue是一个无界的阻塞队列,是一个延时队列,它存储的元素必须实现Delayed接口,其中的元素只有在到期时才能被消费者取走,它底层是使用的PriorityQueue,PriorityQueue底层采用数组实现的二叉堆,队列中的元素根据到期时间进行排序,剩余时间最少的放在堆顶。
因为它几乎趋近于无界,因为底层数组的最大容量限制是Int最大值减8,所以生产者往队列添加元素的时候不会出现阻塞的情况,队列满了就进行扩容
消费者会阻塞
原因1 消费者要取堆顶的元素,堆顶元素还没到期,那么就需要等元素到期才能取走
原因2 消费者要来拿数据,但是发现已经有消费者在等待堆顶数据了,这个后来的消费者也需要等待一会
在这里插入图片描述
通过下面的例子来理解一下队列的使用

package main.java.List;
import java.util.*;
import java.util.concurrent.*;

public class TestList {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        DelayQueue<Task> delayQueue = new DelayQueue();
        delayQueue.add(new Task("A",1000L));
        delayQueue.add(new Task("B",3000L));
        delayQueue.add(new Task("C",7000L));
        delayQueue.add(new Task("D",5000L));

        System.out.println(((Task)delayQueue.take()).getName() + ","+ new Date());
        System.out.println(((Task)delayQueue.take()).getName() + ","+ new Date());
        System.out.println(((Task)delayQueue.take()).getName() + ","+ new Date());
        System.out.println(((Task)delayQueue.take()).getName() + ","+ new Date());
    }

 /**
 * 定义加入队列的元素
 */
 static class Task implements Delayed {
 		// 元素名称 方便区分
        private String name;
		// 什么时间点执行
        private Long delayTime;
		
		// time 是告诉队列这个元素多少时间后可以被取走,比如 1000(1秒),表示元素1秒中后可以被取走,
		// 那么最终delayTime 就是当前时间+time
	    public Task(String name, Long time) {
	        this.name = name;
	        this.delayTime = System.currentTimeMillis()+time;
	    }
		// getDelay 用于取元素时作判断,用元素可以被取走的时间-当前时间,如果<=0,说明时间已经到了,可以取走
	    @Override
	    public long getDelay(TimeUnit unit) {
	        return unit.convert(delayTime-System.currentTimeMillis(), TimeUnit.MILLISECONDS);
	    }
		// compareTo存放元素时进行比较,对元素进行排序
	    @Override
	    public int compareTo(Delayed o) {
	        return (int) (this.delayTime-((Task)o).getDelayTime());
	    }
	    public String getName() {
	        return name;
	    }
	    public void setName(String name) {
	        this.name = name;
	    }
	    public Long getDelayTime() {
	        return delayTime;
	    }
	    public void setDelayTime(Long delayTime) {
	        this.delayTime = delayTime;
	    }
	}
}

执行结果
在这里插入图片描述

1、类信息

通过Delayed Comparable 俩个接口,让元素有了比较和延迟的功能

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E>{
    // DelayQueue中的元素,需要继承Delayed接口
    }

public interface Delayed extends Comparable<Delayed> {
	// 获取元素时,用于判断元素是否可取
    long getDelay(TimeUnit unit);
}

public interface Comparable<T> {
  	// 比较的接口
    public int compareTo(T o);
}
2、重要属性
	// DelayQueue属于阻塞队列,需要保证线程安全。只有一把锁,生产者和消费者使用的是一个lock
	private final transient ReentrantLock lock = new ReentrantLock();
	// 底层使用的是PriorityQueue
    private final PriorityQueue<E> q = new PriorityQueue<E>();
    //leader一般会存储等待栈顶数据的消费者,在写入和消费的过程中,会设置leader
    private Thread leader = null;
    // 等待队列,生产者是不会阻塞的,所以这个Condition是给消费者使用的,
    // 比如堆顶元素还没到期,此时消费者就需要阻塞
    private final Condition available = lock.newCondition();
3、构造方法
	/**
	 * 无参构造 创建一个空队列
	 */
    public DelayQueue() {}
    /**
	 * 有参构造 可以传入一个集合,集合中的元素会被添加到队列中
	 */
    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }
4、写入方法

Delay是无界的,数组可以动态扩容,不需要关注生产者的阻塞问题,他就没有阻塞问题
通过下面的代码,所有写入操作,都是走的offer,研究清楚offer就明白了

   public boolean add(E e) {
       return offer(e);
   }
   public void put(E e) {
       offer(e);
   }
   public boolean offer(E e, long timeout, TimeUnit unit) {
       return offer(e);
   }
  public boolean offer(E e) {
  	  // 加锁
      final ReentrantLock lock = this.lock;
      lock.lock();
      try {
      	  // 调用PriorityQueue的offer方法  
      	  // 这里会根据之前重写Delayed接口中的compareTo方法做排序,然后调整上移和下移操作
          q.offer(e);
          // 取堆顶元素,如果堆顶元素是刚刚添加进来的 将leader置为空,为什么呢,因为这个元素刚刚添加进来,
          // 肯定没有消费者在等着当前这个元素,leader如果不为空,那它也只是在等待之前的堆顶,但是现在的
          // 堆顶已经变了,所以置为空,并且唤醒等待的消费者,让他们重新来获取锁,等待当前的堆顶元素
          if (q.peek() == e) {
              leader = null;
              // 唤醒消费者,避免刚刚插入的数据的延迟时间出现问题。
              available.signal();
          }
          // 返回
          return true;
      } finally {
      	  // 释放
          lock.unlock();
      }
  }
5、读取方法
  public E poll() {
  	 // 加锁
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
     	 // 查看堆顶元素,如果元素为空 或者元素未到期,直接返回null 
         E first = q.peek();
         if (first == null || first.getDelay(NANOSECONDS) > 0)
             return null;
         else
         	 // 到这说明元素不为null,并且已经达到了延迟时间,直接调用PriorityQueue的poll方法
             return q.poll();
     } finally {
         lock.unlock();
     }
  }

下面的poll(long timeout, TimeUnit unit),是允许阻塞的

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) {
            	// 如果元素为空 并且当前消费者的剩余时间已经小于等于0了,直接返回null 
                if (nanos <= 0)
                    return null;
                else
                	// 如果还有剩余时间则等待nanos时间 
                	// 这里挂起线程后,说明队列没有元素,在生产者添加数据之后,会唤醒
                    nanos = available.awaitNanos(nanos);
            } else {
            	// 队列有元素 获取堆顶元素的剩余时间
                long delay = first.getDelay(NANOSECONDS);
                // 有数据的话,先获取数据现在是否可以执行,延迟时间是否已经到了指定时间
                if (delay <= 0)
                	// 时间已经到了,则直接获取
                    return q.poll();
                // =========================================================
                // 如果队列中的元素还没到期,判断消费者的剩余时间是否还能等
                if (nanos <= 0)
                	// nanos 小于等于0,不能等,直接返回null
                    return null;
                // ==================延迟时间没到,消费者可以等一会===================
                // 把first赋值为null
                first = null; 
                // 如果等待的时间,小于元素剩余的延迟时间,消费者直接挂起。
                // 反正暂时拿不到,但是不能保证后续是否有生产者添加一个新的数据,我是可以拿到的。
                // 如果已经有一个消费者在等待堆顶数据了,我这边不做额外操作,直接挂起即可。
                if (nanos < delay || leader != null)
                    nanos = available.awaitNanos(nanos);
                else {
                	// 走到这里,说明当前消费者的阻塞时间可以拿到数据,并且没有其他消费者在等待堆顶数据
                	// 将leader设置为当前线程
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                    	// 让当前消费者阻塞元素的延迟时间
                        long timeLeft = available.awaitNanos(delay);
                        // 重新计算当前消费者剩余的可阻塞时间
                        nanos -= delay - timeLeft;
                    } finally {
                    	// 到了时间,将leader设置为null
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
    	// 没有消费者在等待元素,队列中的元素不为null
        if (leader == null && q.peek() != null)
        	// 只要当前没有leader在等,并且队列有元素,就需要再次唤醒消费者。、
            // 避免队列有元素,但是没有消费者处理的问题
            available.signal();
        // 释放锁
        lock.unlock();
    }
}
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)
                    // 基于优先级队列的poll方法返回
                    return q.poll();
                first = null; 
                // 如果有消费者在等,就正常await挂起
                if (leader != null)
                    available.await();
                // 如果没有消费者在等堆顶数据,我来等
                else {
                    // 获取当前线程
                    Thread thisThread = Thread.currentThread();
                    // 设置为leader,代表等待堆顶的数据
                    leader = thisThread;
                    try {
                        // 等待指定(堆顶元素的延迟时间)时长,
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            // leader赋值null
                            leader = null;
                    }
                }
            }
        }
    } finally {
        // 避免消费者无限等
        // 一般是其他消费者拿到元素走了之后,并且延迟队列还有元素,就执行if内部唤醒方法
        if (leader == null && q.peek() != null)
            available.signal();
        // 释放锁
        lock.unlock();
    }
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值