ArrayBlockingQueue
ArrayBlockingQueue的两个实现细节:
- 底层数据存储是数组实现的;
- 在初始化时确定底层数组大小,队列是有界的;
- 读写操作共用一把锁ReentrantLock lock。
LinkedBlockingQueue
- 底层数据存储是单向链表实现的;
- 队列可设置长度,不设置默认为Integer.MAX_VALUE;
- 读写操作的锁是分离的,用两把锁ReentrantLock takeLock和putLock分别控制
LinkedBlockingQueue生产和消费需要分别移动结点,那么分离的两把锁如何保证不冲突的?
LinkedBlockingQueue是通过头指针和尾指针移动来进行生产和消费的。生产一个元素结点连接到尾指针后面,并且向后移动尾指针指向新的最后一个结点;消费一个元素结点,头指针剔除掉当前指向的结点,并且后移一个结点。按照这种实现方式,如果队列中存在多个数据结点时,两个指针是不会互相干扰的,只有当头尾指针重合指向同一个结点并且同时操作时才可能会产生并发问题。
在LinkedBlockingQueue构造函数中,头指针和尾指针初始化时均指向一个数据为null的结点,此时队列中元素个数为0。换言之,当头指针和尾指针重合时,此时的队列是空的。而且之后操作过程中,队列为空时都会存在一个失效的结点,头尾指针均指向这个结点。源代码如下:
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
队列为空时根据信号量进行控制只有生产线程可以继续工作,而消费线程阻塞,这样不会出现并发问题。而且记录队列元素个数的变量count是原子性操作,在操作完成后才会被更新,并且通过信号量通知,这样可以保证都是在结点添加或删减完成后,其他的线程才可以看到。也就是会存在某一个短暂的时刻,队列中有一个元素,消费线程却无法感知到,做不了任何的操作。
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();
网友评论