ArrayBlockingQueue跟LinkedBlockingQueue差不多,都是解决ConcurrentLinkedQueue的两个问题:队列的无界和读数据不一致问题。实现上LinkedBlockingQueue使用的两把重入锁:putlock和takelock,底层使用单链表。ArrayBlockingQueue使用的一把重入锁,底层使用数组。
为什么ArrayBlockingQueue使用一把锁而LinkedBlockingQueue使用两把锁
-
ArrayBlockingQueue耗时主要在元素的插入和删除,因为ArrayBlockingQueue底层使用的数组,而锁的竞争是其次,虽然说使用两把锁可以减少锁的竞争,但此时的代码复杂度会大大增加,而效率提高并不是很明显
-
LinkedBlockingQueue的元素的插入和删除确是非常快,因为底层使用的单链表,而锁的竞争耗时才是优化的重点。
put("张三")方法
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
enqueue(e);
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
总结:
-
使用了ReentrantLock对方法进行加锁,当元素数量达到数组长度时使用notFull的condition进入await列表
-
入队实际上就是把元素放入数组putIndex位置,然后对putIndex+1后的数值进行判断,如果等于数组长度则putIndex设置为0。说明元素此时已经放过数组一轮的位置了,而且通过之前的判断,现在元素的数量是没有达到数组长度的,所以数组前面的元素肯定有被take的,所以此时应该重新开始放元素
-
然后count++;,记得LinkedBlockingQueue使用的AtomiInteger,而这里直接使用了int,这是因为LinkedBlockingQueue使用的两把锁,put和take互不干扰,所以count有多线程操作,而ArrayBlockingQueue使用的是一把锁,put和take互斥。这里用int完全不会有多线程操作
-
放了之后直接用notEmpty.signal();而LinkedBlockingQueue那边是有判断c==0,感觉还是LinkedBlockingQueue逻辑好一点,不判断的话可能就空调用了signal。同一个作者写的,为什么会写出两个逻辑,不是很理解
take()方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
dequeue();
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
总结:
-
使用了ReentrantLock对方法进行加锁,当元素数量为0时使用notEmpty的condition进入await列表
-
拿到数组takeIndex位置上的元素,然后把数组takeIndex位置设置为null
-
然后对takeIndex+1后的数值进行判断,如果等于数组长度则takeIndex设置为0。说明元素此时已经取过数组一轮的位置了,而且通过之前的判断,现在元素的数量不为0,所以数组前面位置肯定有被put的,所以此时可以重新开始取元素。
-
count--;和notFull.signal();跟put原理一样,这里不多说了