前言
本节就一起探讨一下什么是阻塞队列,一起来看下吧!
什么是阻塞队列
我们先说一下为什么要引入阻塞队列。我们知道服务器的资源是有限的,就拿典型的生产者和消费者模型来讲。
消费者如果没有东西可以消费了,但是它还是在一直执行,这无疑是在浪费系统资源,所以我们需要阻塞消费者,换过来同理。
生产者没有东西可生产了或者说没有地方存储它生产的东西,这时候我们就需要阻塞生产者。
然而,在开发中,这种模型往往运行在多线程环境中,需要对资源共享从而达到更高的性能,但这样也会造成线程安全问题,比如死锁、重复消费 所以阻塞队列就是帮我们解决这些问题的。
BlockingQueue
下面我们就一起看一下Java中给我们提供了哪些好用的工具,先给大家介绍一下BlockingQueue, 本节我们主要讲它的用法。
BlockingQueue是Java util.concurrent包下的类,BlockingQueue提供了「线程安全的队列访问方式」,并发包下很多高级同步类的实现都是基于BlockingQueue实现的。
BlockingQueue一般用于生产者-消费者模式,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。「BlockingQueue就是存放元素的容器」。
public interface BlockingQueue<E> extends Queue<E> {....}
它本身是一个接口,我们看下它常用的实现类。
ArrayBlockingQueue
一个用数组实现的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行排序, 支持公平锁和非公平锁。
- add() 添加元素。
// 初始化 容量为3
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.add("1"));
System.out.println(queue.add("2"));
System.out.println(queue.add("3"));
System.out.println(queue.add("4"));
我们发现添加到4的时候,报错了Exception in thread "main" java.lang.IllegalStateException: Queue full。
- remove() 移除元素,如果为空,会返回异常。
// 初始化 容量为3
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.add("1"));
System.out.println(queue.add("2"));
System.out.println(queue.add("3"));
// System.out.println(queue.add("4"));
System.out.println(queue.remove());
System.out.println(queue);
打印:
true
true
true
1
[2, 3]
从结果来看,符合FIFO规则,如果我想移除指定元素怎么办呢?很简单,使用remove("3")。
- offer() 和add方法类似,但是它不会报错,它会返回false, offer(e,time,unit)支持超时。
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer("1"));
System.out.println(queue.offer("2"));
System.out.println(queue.offer("3"));
System.out.println(queue.offer("4"));
true
true
true
false
- poll() 与remove相似,但它不会返回异常而是返回null, poll(time,unit)。
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.poll());
null
- take() 与 poll类似, 但它会造成线程阻塞。
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.take());
}
运行后,我们发现程序被阻塞了。
- put() 添加元素,当容器满的时候,会造成线程阻塞。
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
queue.put("1");
queue.put("2");
}
运行后发现,执行到2的时候被阻塞了。
以上是它的常用方法,其它的实现类就不一一给大家演示了,方法差不多,都是基于BlockingQueue接口,可以自己试着运行一下看看。
LinkedBlockingQueue
一个由链表结构组成的有界队列,此队列的长度为Integer.MAX_VALUE。
SynchronousQueue
是一个不存储元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。支持公平锁和非公平锁。SynchronousQueue的一个使用场景是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个上节我们讲线程复用的时候遇到过。
LinkedTransferQueue
是一个由链表结构组成的无界阻塞队列,相当于其它队列,LinkedTransferQueue队列多了transfer和tryTransfer方法。
PriorityBlockingQueue
是一个支持线程优先级排序的无界队列,默认自然序进行排序,也可以自定义实现compareTo()方法来指定元素排序规则,不能保证同优先级元素的顺序。
DelayQueue
一个实现PriorityBlockingQueue实现延迟获取的无界队列,在创建元素时,可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素,在缓存设计和定时任务调度中经常会遇到。
结束语
本节主要给大家介绍了常用的阻塞队列以及它的基本使用。