谈谈 BlockingQueue 的使用

什么是 BlockingQueue?

简介

    BlockingQueue 是由数组支持的有界阻塞队列。此队列对元素FIFO(先进先出)进行排序。队列的开头是已在队列中最长时间的元素。队列的尾部是最短时间出现在队列中的元素。新元素插入到队列的尾部,并且队列检索操作在队列的开头获取元素

    此类支持可选的公平性策略,用于订购正在等待的生产者和使用者线程。默认情况下,不保证此排序。但是,将公平性设置为true构造的队列将按FIFO顺序授予线程访问权限。公平通常会降低吞吐量,但会减少可变性并避免饥饿
在这里插入图片描述

定义

  • 当队列为空时,从队列中获取元素的操作将会被阻塞(当试图从空的队列中获取元素的线程将会被阻塞,直到其它线程往空的队列插入新的元素)

  • 当队列为满时,从队列中添加元素的操作将会被阻塞(当试图向已满的队列中添加新元素的线程将会被阻塞,直到其它线程从队列中移除 一个或者多个或者完全清空,使队列变得空闲起来并后续新增)

方法类型 抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用
抛出异常 当阻塞队列满时,再往队列里 add 插入元素会抛出 IllegalStateException(“Queue full”);当阻塞队列为空时,再从队列里 remove 移除元素会抛出 NoSuchElementException
特殊值 插入方法,成功返回 true,失败返回 false;移除方法,成功返回出队列的元素,队列里没有就返回 null
一直阻塞 当阻塞队列满时,生产者线程继续往队列里 put 元素,队列会一直阻塞生产者线程直到 put 数据或者响应中断退出;当队列为空时,消费者线程视图从队列里 take 元素,队列会一直阻塞消费者直到队列可用
超时退出 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出

操作分解

添加元素

假如现在队列里有一个元素,第一个元素也叫头节点
在这里插入图片描述
然后我们添加一个元素,当我们添加一个元素时,那么这个新添加的元素就是尾节点了(头节点指向尾节点)
在这里插入图片描述
同样地,再添加一个元素也是一样
在这里插入图片描述

移除元素

在上图中移除一个元素是移除最先添加的元素,也就是 11
在这里插入图片描述
从上图中再移除一个元素,也就是 12
在这里插入图片描述

动画演示

队列遵循的是先进先出原则
在这里插入图片描述

抛出异常

IllegalStateException

那么这个抛出异常怎么理解呢?可以来一段代码测试一下

package juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @author Woo_home
 * @create by 2020/3/14
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
    	// 创建一个 ArrayBlockingQueue 并初始化大小为 3
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        // 往队列里面添加 3 个元素,add 成功会返回 true
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
    }
}

输出:
在这里插入图片描述
因为我们已经将 ArrayBlockingQueue 初始化为 3,所以添加 3 个元素的时候是可以的,但是当我们在添加一个元素的时,就会抛出一个异常,如下:

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));

		// 再次添加一个元素
        System.out.println(queue.add("x"));
    }
}

抛出异常了,异常为 Queue full(队列已经满了)
在这里插入图片描述
而 ArrayBlockingQueue 的 add(E e) 方法的源码如下:

可以发现当我们使用 add() 方法的时候使用的是 ArrayBlockingQueue 的 add() 方法
在这里插入图片描述
而 ArrayBlockingQueue 的 add 方法实际调用的是父类 AbstractQueue 的 add() 方法,AbstractQueue 的 add() 方法在每次添加元素时都会使用 offer 方法(该方法是实现 Queue 接口的方法)进行判断,判断此时的队列是否已经满了,如果没满,则添加元素并且返回 true,否则抛出 IllegalStateException(“Queue full”) 异常

add(E e)

在这里插入图片描述

NoSuchElementException

package juc;

/**
 * @author Woo_home
 * @create by 2020/3/14
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        queue.add("a");
        queue.add("b");
        queue.add("c");

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
}

这个程序往队列里面添加添加三个元素,然后取出三个元素,上面已经讲过,队列是遵循先进先出原则,移除是移除最先添加的元素

输出:
在这里插入图片描述
这时的队列元素已经全部移除,已经为空了,当再次 remove 时则会抛出 NoSuchElementException 异常,如下:

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        queue.add("a");
        queue.add("b");
        queue.add("c");

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());

		// 经过上面的 remove,队列此时已为空
        System.out.println(queue.remove());
    }
}

输出:
在这里插入图片描述
当队列为空时,在往队列里 remove 移除元素会抛出 NoSuchElementException 异常

特殊值

offer(E e)

看下下面这段代码

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
        System.out.println(queue.offer("x"));
    }
}

输出:
在这里插入图片描述
offer 的操作跟 add 差不多,都是添加元素,但是 offer 方法添加元素当添加元素的数量大于初始化的值时会返回一个 false 而不是抛出异常

poll()

poll() 操作跟 remove() 操作差不多,都是移除元素,但是 poll 的移除是成功返回队列的元素,队列里没有的元素就返回 null 而不是抛出异常

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));

        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

输出:
在这里插入图片描述

阻塞

  • 当阻塞队里满时,生产者线程继续往队列里 put 元素,队列会一直阻塞生产者线程知道 put 数据 or 响应中断退出
  • 当阻塞队列为空时,消费者线程视图从队列里 take 元素,队列会一直阻塞消费者线程直到队列可用
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        queue.put("a");
        queue.put("b");
        queue.put("c");

        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
    }
}

输出:
在这里插入图片描述

超时

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
        // 如果队列满了超过 3 s,则返回 false
        System.out.println(queue.offer("a", 3L,TimeUnit.SECONDS));
    }
}

当使用 offer 超时函数时,如果队列已满,则超时等待设定的时间,如果时间过了还没有空位置的话,则返回 false
在这里插入图片描述

发布了227 篇原创文章 · 获赞 1032 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/Woo_home/article/details/104858722