说明
LinkedBlockingQueue是基于链表实现的,所以说它不同于ArrayBlockingQueue,它
的容量限制被写死在代码了,即int的最大值,至于其使用类似于ArrayBlockingQueue
原理说明
成员属性
从属性上看,这是个双锁控制的同步队列,不同于ArrayBlockingQueue,ArrayBlockingQueue是一把锁双条件控制,这里不难想象,它加锁的方式应该是估计应该 是 每添加一个节点,就直接take,put锁全加上,确保take和put不会同时在删除一个节点,又同时在该删除节点后面新增节点,实际上并非如此
put方法
public void put(E e) throws InterruptedException {
阻塞队列一般都会有的非空异常判断
if (e == null) throw new NullPointerException();
int c = -1;
新增一个节点
Node<E> node = new Node<E>(e);
获取put锁
final ReentrantLock putLock = this.putLock;
获取当前容纳元素的数量
final AtomicInteger count = this.count;
put上锁
putLock.lockInterruptibly();
try {
检查队列是否已经是最大容量了
while (count.get() == capacity) {
容量达到最大put条件阻塞
notFull.await();
}
添加一个节点到队列中
enqueue(node);
重新计算当前容纳的元素个数
c = count.getAndIncrement();
if (c + 1 < capacity)
获取put方法放元素
notFull.signal();
} finally {
释放put锁
putLock.unlock();
}
判断是否有元素在集合中
if (c == 0)
唤醒take方法消费元素
signalNotEmpty();
}
从上面代码看,并不是直接上双锁来确保take与put方法的调用,再看节点添加方法
节点赋值,这个方法一般来说是绝对不是线程安全的,纵使添加了和put相关的锁
对于take来说,这里的操作是有可能被take介入的,倘若take介入,是否会存在问题
譬如如下问题: last.next = node 刚刚赋值结束,take方法需要获取last节点,由于
next节点还没来的即装换为last节点,导致take方法获取的不是最新的last节点,继而导致
丢失了新增的这个node节点,这种情况必然是存在的,那这就说明LinkedBlockingQueue
是存在并发隐患的吗?事实是如此吗,见下图分析
private void enqueue(Node<E> node) {
last = last.next = node;
}
头节点:发生上述情况,而此时last是null,不存在上述描述的问题
中间节点:操作的位置都不一样了,不存在竞争节点的问题
原来是我想多了,再者这个容器压根就不可能同时存在2个take方法在执行,也不可能
存在2个put方法在执行,线程的临界点只会是最多存在一个take一个put方法
不过看看remove方法就,这不就是直接上双锁吗,哈哈,只能说这LinkedBlockingQueue的锁上的很有料啊
public boolean remove(Object o) {
if (o == null) return false;
/**
void fullyLock() {
putLock.lock();
takeLock.lock();
}
*/
fullyLock();
try {
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
总结:1) LinkedBlockingQueue是一个容量特别大(int 最大值)的容器
2) 这吗大的容量通常来说对于put方法几乎都是排队执行的没什么阻塞