阻塞队列是一种队列。所以一方面是可以存储数据的,(在线程池中,当提交的任务不能被立即得到执行的时候,线程池就会将提交的任务放到一个阻塞的任务队列中来),同时它还有阻塞线程的作用(消费者生产者模式)。主要体现在以下
- 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放
入队列。 - 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有
空的位置,线程被自动唤醒。
java中默认的阻塞队列
- ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列。
- LinkedTransferQueue:由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
阻塞队列的主要方法
ArrayBlockingQueue
|
|
由以上成员变量和构造方法可知:ArrayBlockingQueue用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下
不保证访问者公平的访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。
|
|
需要注意的是数组使用了两个游标变量:takeIndex和putIndex,配合这两个变量之后数组的使用就像是一个环形队列一样了。并且每次存或取得时候都会判断,如果数组里的数据达到了capacity,就不再存取,即putIndex存一圈后又从0开始时永远不会到达takeIndex所在位置。
以上就是add、offer、put方法差异的原因。
LinkedBlockingQueue
|
|
由以上成员变量和构造方法可知:LinkedBlockingQueue使用链表来作为队列的数据结构,存和取的操作使用了不同的锁
|
|
取节点稍微复杂点,用图来描述下:
代码里的实现并不是直接将first节点拿掉然后将head节点的next指向second节点,而是直接将first设置为头结点 并且将头结点item值设为null,这样first节点就转变成了空节点。
|
|
通过以上代码可知,add、offer、put的差异就在于调用enqueue前是否等待以及等待时长。需要注意的是每次存完数据后都有notFull.signal()这段代码,这是因为LinkedBlockingQueue采用了双锁的机制,导致存完数据后不单要唤醒取线程,还要唤醒可能存在的存线程。如下图所示,ArrayBlockingQueue的put线程从acquireLock到releaseLock这阶段不会有存或者取线程干扰。LinkedBlockingQueue的put线程会阻塞后续的put线程,但是不会阻塞take线程,这就导致一种可能:put线程发现队列满了,在准备阻塞到阻塞这段时间让出了cpu碎片并且完成了2次take操作,虽然2次take都会唤醒put线程,但是此时put线程还没阻塞。所以这就需要后续的put线程或者take线程取唤醒了。