【并发编程】Java 阻塞队列

1. 简介

  阻塞队列(BlockingQueue)是Java中一种线程安全的存取队列,适用于生产者消费者场景,支持两种附件操作:

  • 生产者线程不断的往阻塞队列中放入数据,直到队列放满为止。队列放满后,生产者线程阻塞等待消费者线程获取数据。
  • 消费者线程不断的从阻塞队列中火球数据,直到队列为空。队列为空后,消费者线程阻塞等待生产者线程放入数据。

2. BlockingQueue提供四种不同的处理方式

编号 处理方式 插入方法 移除方法 检查方法
1 抛出异常 add(e) remove(e) element()
2 返回特殊值 offer(e) poll() peek()
3 一直阻塞 put(e) take(e)
4 超时退出 offer(e, timeout, timeunit) poll(e, timeout, timeunit)

  四种方式解释:

  • 抛出异常
    • add方法:插入数据时,如果队列已满,则抛出IllegalStateException异常,否则返回true。
    • remove方法:移除数据时,如果队列存在此元素,则删除成功,返回true,否则返回false。如果存在多个元素,则仅移除一个元素并返回true。需要注意:remove(e)是BlockingQueue接口的方法,remove()是Queue接口的方法。
    • element方法:检查元素时,如果队列为空,则抛出NoSuchElementException异常,否则返回队列头部元素。element()是Queue接口的方法。
  • 返回特殊值
    • offer方法:插入数据时,如果队列未满,则插入成功返回true,否则返回false。当使用有界队列时,建议使用offer方法。
    • poll方法:移除数据时,如果队列不为空,则移除队列头部元素并返回,否则,返回null。poll()是Queue接口的方法。
    • peek方法:检查元素时,如果队列为空,则返回null,否则返回队列头部元素。peek()是Queue接口的方法。
  • 一直阻塞
    • put方法:插入数据时,如果队列为空,则插入成功,否则进行阻塞等待队列可用,若等待时被中断,则抛出InterruptedException异常。
    • take方法:检查元素时,如果队列不为空,则返回队列头部元素,否则进行阻塞等待队列插入数据,若等待时被中断,则抛出InterruptedException异常。
  • 超时退出
    • offer方法:插入数据时,如果队列为空,则插入成功返回true,否则进行阻塞等待队列可用,若等待时被中断,则抛出InterruptedException异常,若在指定时间内仍然不可用,则返回false。
    • poll方法:检查元素时,如果队列不为空,则返回队列头部元素,否则进行阻塞等待队列插入数据,若等待时被中断,则抛出InterruptedException异常,若在指定时间内仍然不可用,则返回null。
        注意:Queue队列不能插入null,否则会抛出NullPointerException异常。

3. Java中常见的七种阻塞队列

3.1 ArrayBlockingQueue

  ArrayBlockingQueue是基于数组的有界阻塞队列,按照FIFO(先进先出)顺序。队列头部元素是出现时间最长的元素,队列尾部元素是出现时间最短的元素。新元素被插入到队列的尾部,队列检索操作获得队列头部的元素。
  该队列创建时需要指定容量大小,创建后其容量不能更改。如果队列已满,继续插入元素时会阻塞队列,如果队列为空,获取元素时同样会阻塞队列。
  该队列中只有一把锁,写锁和读锁未分离,并发控制采用了经典的two-condition(notEmpty、notFull)算法。
  该队列支持公平锁和非公平锁,默认非公平锁。在创建时,可以指定为公平锁。公平锁通常会降低吞吐量,但会降低线程的可变性并避免饥饿。

3.2 LinkedBlockingQueue

  LinkedBlockingQueue是基于链表的可选有界阻塞队列,按照FIFO(先进先出)顺序。
  该队列创建时可以指定容量,也可不指定,不指定则默认为Integer.MAX_VALUE,因此称为无界队列。
  该队列中有两把把锁,写锁和读锁分离。

3.3 PriorityBlockingQueue

  PriorityBlockingQueue是基于数组的无界阻塞队列,支持优先级排序。
  该队列创建时可以指定初始化容量,不指定默认为11。
  该队列的最大容量为Integer.MAX_VALUE - 8,因此称为无界。
  该队列不会阻塞生产线程,查看源码可知插入方法offer(e, timeout, timeunit)直接调用了offer(e)方法,即插入数据方法offer(e)和offer(e, timeout, timeunit)永远不会返回fasle,但是移除方法take()仍然会阻塞。
  该队列通过数据复制实现扩容,如果新数组的大小超过最大容量(为Integer.MAX_VALUE - 8),则抛出OutOfMemoryError异常。
  该队列可以在创建时指定元素的排序规则,默认使用元素的自然顺序排序。

3.4 DelayQueue

  DelayQueue是基于PriorityQueue的无界阻塞队列,支持延迟获取元素。
  该队列存放的元素必须实现Delay接口。
  该队列存放的元素,如果没有过期元素,则没有头部,移除方法poll()返回null。
  该队列中元素的getDelay(TimeUnit.NANOSECONDS)方法返回小于或等于零时,就会过期。
  该队列中存在未过期的元素,但是和过期元素同样对待,例如获取队列大小时,返回的时过期和未过期元素的和。
  该队列适用于在一段时间后进行的业务处理场景,比如:订单在半个小时未支付则关闭,超过一定时间关闭空闲连接等。

3.5 SynchronousQueue

  SynchronousQueue是不存储元素的阻塞队列。
  该队列执行每次插入操作时必须等待另一个线程执行移除操作,反之亦然。
  该队列中没有任何容量,甚至一个容量也没有。
  该队列中不能使用peek方法检查元素,因为当存在线程将要删除时该元素才存在。
  该队列不支持迭代,因为没有元素可以迭代。
  该队列的头部元素是第一个排队的插入线程试图添加到队列中的元素;如果没有这样的排队线程,则没有可用于删除的元素,移除时poll()将返回null。
  该队列支持公平锁和非公平锁,默认使用非公平锁。在创建队列时,可以指定为公平锁,即按照先进先出顺序分配线程。
  该队列适用于传递性设计(handoff designs),在这种设计中,运行在一个线程中的对象必须与运行在另一个线程中的对象同步,以便向它传递一些信息、事件或任务。

3.6 LinkedTransferQueue

  LinkedTransferQueue是基于链表的无界阻塞队列,按照FIFO(先进先出)顺序。
  该队列在创建是不需要指定容量大小,但最大容量为Integer.MAX_VALUE,因此称为无界队列。
  该队列支持常规的put(e)方法插入元素,也可以使用特定方法transfer(e)进行阻塞插入,其阻塞和非阻塞插入的元素可以共存。
  该队列中的插入的元素如果存在消费者通过移除方法take(e)或poll(e, timeout, timeunit)获取时,会立即将元素传递给消费者,否则将该元素插入到队列的尾部,并等待该元素被消费者接收。

3.7 LinkedBlockingDeque

  LinkedBlockingDeque是基于链表的可选有界阻塞队列,按照FIFO(先进先出)或FILO(先进后出)顺序。
  该队列创建时可以指定容量,也可不指定,不指定则默认为Integer.MAX_VALUE,因此称为无界队列。
  该队支持同时从队列的头和尾进行插入或移除操作。

posted @ 2021-06-20 15:44  C3Stones  阅读(335)  评论(0编辑  收藏  举报