BlockingQueue

BlockingQueue 接口定义了很多插入和删除的方法,这里总结梳理如下:

添加

  1. 容器满了,抛异常
    add(e)
  2. 返回是否成功
    offer(e)
  3. 容器满了,阻塞
    put(e)
  4. 容器满了,等待超时退出
    offer(e, time, unit)

删除

  1. 容器没有元素,抛异常
    remove()
  2. 返回是否成功
    remove(obj)
    poll()
  3. 容器没有元素,阻塞
    take()
  4. 容器没有元素,等待超时退出
    poll(time, unit)

其他

  1. 获取队头元素,但不出容器,没有则抛异常
    element()
  2. 获取队头元素,但不出容器,没有不抛异常
    peek()

总结

image.png

有界阻塞队列

ArrayBlockingQueue

  • 数组实现的有界阻塞队列;
  • 队列按照先进先出原则对元素进行排列;
  • 默认不保证线程公平访问队列;
  • 所谓公平访问队列,是指按照阻塞线程的顺序访问队列。

LinkedBlockingQueue

  • 链表实现的有界阻塞队列;
  • FIFO
  • 未设置容器大小,默认为 Integer.MAX_VALUE

SynchronousQueue

  • 不存储元素的有界阻塞队列
  • 每一个 put 操作,必须对应另外一个线程的 take 操作
  • 队列实现公平访问;用实现不公平访问

LinkedBlockingDequeue

  • 双向链表实现的有界阻塞队列
  • 双向链表指的是:可从队头、队尾 插入或删除元素
  • 未设置容器大小,默认为 Integer.MAX_VALUE

共性

  • put 操作在容器满时,会阻塞
  • take 操作在容器没有元素时,会阻塞

无界阻塞队列

PriorityBlockingQueue

  • 基于 实现的具有 优先级 的无界阻塞队列
  • 默认按照自然排序升序排列
  • 可通过元素的内部排序,或外部排序自定义顺序

DelayQueue

  • 内部基于 PriorityQueue 实现支持延时获取元素的无界阻塞队列
  • 队列内元素必须实现 Delayed 接口

LinkedTransferQueue

  • 链表实现的无界阻塞队列
  • 与其他阻塞队列不同的是,多提供了 transfer, tryTransfer 方法
  • transfer 会阻塞到有消费者从队列中消费元素
  • tryTransfer 不带时间的方法,会直接返回;带时间的方法,会根据时间阻塞到有消费者消费

总结

  • put, take 均不阻塞

阻塞队列的道与术

上面讲了阻塞队列如何使用,但是那仅仅是术。只有了解了道(阻塞队列的设计),我们才可以在即使是换了语言的情况下,也能模仿 JAVA 阻塞队列的设计思路,自己编写阻塞队列。

问题一:选择合适的数据结构

数据结构,选数组,还是链表都可以。只要清楚数组,链表各自的优缺点,并进行选择即可。
考虑到需要高速存取,不希望有内存碎片。因此,选择数组会相对合适。在考虑到容器的重复使用,我们就需要2 个指针,存放当前消费者消费到哪个位置,生产者最后生产的元素在哪个位置。

其实以上描述的是循环数组的结构。

image.png

问题二:容器满了,生产者如何实现不在生产;容器没元素,消费者如何实现不再消费

这里用 伪代码 代码描述该问题。

public void producer() {
    while(数组长度 == 容器内的元素个数) {
      生产者休眠
    }
}
public void consumer() {
    while(容器内的元素个数 == 0) {
      消费者休眠
    }
}

问题三:生产者应该在什么时候唤醒消费者;消费者应该在什么时候唤醒生产者

将上诉代码稍微改造下。我们只需要在 producer 方法被调用后,就可以通知消费者消费了

public void producer() {
    while(数组长度 == 容器内的元素个数) {
      生产者休眠
    }
    生产
    唤醒消费者
}
public void consumer() {
    while(容器内的元素个数 == 0) {
      消费者休眠
    }
    生产
    唤醒生产者
}

问题四:如何保护共享资源

对于共享资源的保护,那只能使用锁来保护了

public void producer() {
加锁
    while(数组长度 == 容器内的元素个数) {
      生产者休眠
    }
    生产
    唤醒消费者
释放锁
}
public void consumer() {
加锁
    while(容器内的元素个数 == 0) {
      消费者休眠
    }
    生产
    唤醒生产者
释放锁
}

总结

上诉的代码其实是非常经典的多生产者与多消费者的代码。这边做的改造,仅仅是使用循环数组作为存储容器。

参考


心无私天地宽
513 声望22 粉丝