TOP 带着问题看源码
ArrayBlockingQueue 实现原理
ArrayBlockingQueue 应用的场景
ArrayBlockingQueue 和 LinkedBlockingQueue 区别
1. 基本介绍 ArrayBlockingQueue 是一个基于数组实现的线程安全的阻塞队列。
其实现了阻塞队列 BlockingQueue 接口和基本队列操作 AbstractQueue 接口
2. 成员变量分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 final Object[] items;int takeIndex;int putIndex;int count;final ReentrantLock lock;private final Condition notEmpty;private final Condition notFull;transient Itrs itrs = null ;
3. 构造方法分析 1 2 3 4 5 6 7 8 9 10 11 12 13 public ArrayBlockingQueue (int capacity) { this (capacity, false ); } public ArrayBlockingQueue (int capacity, boolean fair) { if (capacity <= 0 ) throw new IllegalArgumentException(); this .items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
4. 核心方法分析 4.1 入队列操作 4.1.1 enqueue(E x) 核心逻辑就是往数组中插入一条数据,然后更新 putIndex,唤醒 “队列不为空” 条件对应的条件队列。
1 2 3 4 5 6 7 8 9 10 11 12 private void enqueue (E x) { final Object[] items = this .items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0 ; count++; notEmpty.signal(); }
4.1.2 offer(E e) 队列对外暴露的入队列 API,其实就是调用了一下 enqueue 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public boolean offer (E e) { checkNotNull(e); final ReentrantLock lock = this .lock; lock.lock(); try { if (count == items.length) return false ; else { enqueue(e); return true ; } } finally { lock.unlock(); } }
4.1.3 put(E e) 队列对外暴露的入队列 阻塞API,如果队列已满,会进入阻塞状态,直到 ”队列没有满“ 条件满足,才会插入新的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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(); } }
4.2 出队列操作 4.2.1 dequeue() 核心逻辑就是从数组中取出一条数据,然后更新 takeIndex,唤醒 “队列没有满” 条件对应的条件队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; }
4.2.2 poll() 队列对外暴露的取队列 API,如果取完,返回null,没取完就调用 dequeue 方法
1 2 3 4 5 6 7 8 9 public E poll () { final ReentrantLock lock = this .lock; lock.lock(); try { return (count == 0 ) ? null : dequeue(); } finally { lock.unlock(); } }
4.2.3 take() 队列对外暴露的取队列 阻塞API,如果队列已空,会进入阻塞状态,直到 “队列不为空” 条件满足,才会继续取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public E take () throws InterruptedException { final ReentrantLock lock = this .lock; lock.lockInterruptibly(); try { while (count == 0 ) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } }
4.2.4 peek() 队列对外暴露的只看不取 API,逻辑很简单,就是直接按照 takeIndex 查看元素,但是不置空也不维护 takeIndex
1 2 3 4 5 6 7 8 9 10 11 12 13 public E peek () { final ReentrantLock lock = this .lock; lock.lock(); try { return itemAt(takeIndex); } finally { lock.unlock(); } } final E itemAt (int i) { return (E) items[i]; }
通过上面的核心方法分析,回到问题 TOP 1 我们可以明白其实现原理就是采用 可重入非公平锁来保证线程安全,通过在数组的入口和出口来互相更新条件变量的唤醒条件来实现阻塞队列。
5. 总结 整体设计我们可以发现设计的很巧妙,既有阻塞 API,也有不阻塞线程安全的 API。回到问题 TOP 2 其应用场景不难想象,只要是涉及到内存中的生产者-消费者模型的都可以使用它来暂存数据。
关于最后的问题 TOP 3 ,其区别主要是底层数据结构的选择,整体的阻塞设计和逻辑都是一样的。