BlockingQueue 阻塞队列详解


一. 阻塞队列介绍

阻塞队列指的是当队列已满的时候,入队操作阻塞,而当队列为空的时候,出队操作阻塞。同时,在种类的区分的时候还区分单端和双端区别,单端就是以 Queue 为名的队列,而双端是以 Deque 为名的队列。

阻塞队列实现的接口 BlockingQueue 中有多个存储、提取以及检查的方法,有些方法虽然看起来重复,但是每个方法的作用是不一样的。

1. 队列中各种方法的解释

存储

  • add()(返回值 boolean):和 offer() 作用相同,不一样的是 add() 如果没有空间存储,也就是元素插入失败的时候会抛出异常 IllegalStateException。
  • offer()(返回值 boolean):在指定容量的队列中插入元素,如果插入成功返回 true,插入失败返回 false。更适合在指定容量的队列使用。
  • put()(返回值 void):不会返回内容,如果插入元素的时候,队列不为空,则会一直等待队列空出。

提取

  • take():提取队列中的头元素,如果元素状态不可提取,则会等到状态变为可提取状态。
  • poll():提取队列中的头元素,如果队列为空,则会返回 null。
  • remove():和 poll() 方法相同,不同的是如果队列为空,则会抛出异常
    NoSuchElementException。

检查

  • peek():检查队列头部是否有元素,不会移除元素。如果队列为空,则会返回 null。
  • element():检查队列头部是否有元素,不会移除元素。如果队列为空,则会抛出异常
    NoSuchElementException。

2. 不同类型的队列的对比

队列类型 数据结构 是否阻塞 是否有界 特点
ArrayBlockingQueue 数组 FIFO 规则排列
LinkedBlockingDeque 链表 自定义(没有填写容量则是无界) 双端队列
LinkedBlockingQueue 链表 自定义(没有填写容量则是无界) FIFO 规则排列
DelayQueue 链表 可设置延迟时间
LinkedTransferQueue 链表 FIFO 规则排列,SynchronousQueue 的升级版
PriorityBlockingQueue 数组 可设定排序规则
SynchronousQueue 栈或队列 不存储元素 不存储元素

二. 阻塞队列的类型

1. ArrayBlockingQueue

ArrayBlockingQueue 是一个用数组结构实现的有界阻塞队列,队列按照 FIFO 的规则进行元素的排列,也就是说队列的头结点是等待时间最长的节点,而尾节点则是等待时间最短的节点,新节点会插入到尾节点的后面。

有界的定义是,一旦队列初始化并且指定了大小以后,之后就再也不能改变队列的大小。

在 ArrayBlockingQueue 初始化的时候,还可以选择是否选择公平策略,公平策略主要决定的是队列中维护的 ReentrantLock 的策略。在 ReentrantLock 中知道,公平策略会决定是否是等待时间最长的线程出列,如果选择了公平策略,则为了维护这个策略会牺牲一定的效率。

public ArrayBlockingQueue(int capacity, boolean fair) {
     
    if (capacity <= 0)  
        throw new IllegalArgumentException();  
    this.items = new Object[capacity];  
    lock = new ReentrantLock(fair);  
    notEmpty = lock.newCondition();  
    notFull =  lock.newCondition();  
}

参考例子

ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(10);  
queue.offer("1");  
queue.offer("3");  
queue.offer("2");  
  
while (queue.peek() != null) {
     
    System.out.println(queue.poll());  
}

入列和出列

/**
* 入列
*/
private void enqueue(E x) {
   
	// 插入元素
	final Object[] items = this.items;
    items[putIndex] = x;

	// 指针指向第一个元素
    if (++putIndex == items.length)  
        putIndex = 0;  
    count++;
    notEmpty.signal();  
}

/**
* 出列
*/
private E dequeue() {
   
    // 元素出列
    E x = (E) items[takeIndex];  
    items[takeIndex] = null;
    // 指针指向第一个元素
    if (++takeIndex == items.length)  
        takeIndex = 0;  
    count--;
	// 删除迭代器队列中的元素
    if (itrs != null)  
        itrs.elementDequeued();  
    notFull.signal();  
    return x;  
}

2. DelayQueue

DelayQueue 延迟队列是一个由链表结构组成的无界阻塞队列,只有在其中元素到延迟的时间时候才能取出,在实现的时候,使用了 PriorityQueue 作为元素的存储队列。

DelayQueue 中的元素必须实现 Delayed 接口,接口中有一个方法 getDelay(),用来获取元素剩余的延迟时间,只有在延迟时间到了才能取出元素。在 ScheduledThreadPoolExecutor 中有该方法的实现例子。

public long getDelay(TimeUnit unit) {
     
    return unit.convert(time - now(), NANOSECONDS);  
}

参考例子

DelayQueue 在使用的时候需要注意,因为 DelayQueue 中维护了一个优先级队列 PriorityQueue,DelayQueue 存储或者提取元素都是从 PriorityQueue 中提取。

而 PriorityQueue 存储元素的时候,会根据自定义实现的 compareTo() 方法来进行堆排序维护元素的顺序,所以在最后打印出来的元素顺序和存储顺序不一定相同。

public class Test {
     
  
    static class Element implements Delayed {
     
  
        private Object ele;  
  
        private final long createTime;  
        private long delay;  
  
        {
     
            this.createTime = System.currentTimeMillis();  
        }  
  
        public Element(Object ele, long delay) {
     
            this.ele = ele;  
            this.delay = delay;  
        }  
  
        @Override  
        public long getDelay(TimeUnit unit) {
     
            return unit.convert(createTime + delay - System.currentTimeMillis(), TimeUnit.MILLISECONDS);  
        }  
  
        @Override  
        public int compareTo(Delayed o) {
     
            return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));  
        }  
  
        @Override  
        public String toString() {
     
            return String.format("Element: ele:[%s], delay:[%d]", ele, delay);  
        }  
    }  
  
    public static void main(String[] args) {
     
        DelayQueue<Element> delayQueue = new DelayQueue<>();  
        delayQueue.offer(new Element("元素1", 1000));  
        delayQueue.offer(new Element("元素2", 4000));  
        delayQueue.offer(new Element("元素3", 3000));  
        delayQueue.offer(new Element("元素4", 2000));  
  
        while (delayQueue.size() != 0) {
     
            try {
     
                Element poll = delayQueue.take();  
                System.out.println(poll);  
            } catch (InterruptedException e) {
     
                e.printStackTrace();  
            }  
        }  
    }  
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瞎叨叨的一天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值