首先了解ArrayBlockingQueue : 阻塞式数组队列 。 底层以数组的方式实现, 可以看做是 循环数组。
首先了解概念(转载来的):
- 阻塞式集合(Blocking Collection): 这类集合一般在添加或删除数据,如果集合已满或为空时,则调用添加和删除方法的线程会被阻塞,直接该方法可以成功执行
- 非阻塞式集合(Non-Blocking Collection):这类集合一般在添加或删除数据,如果方法不能立即执行时,则会返回
Null
或抛出异常,但调用该方法的线程不会被阻塞.
add(E)
,offer(E)
,pub(E)
都是这队列尾部加入元素E,如果队列不满,则加入成功,并立即返回.如果队列满了,那么
add
会抛出IllegalStateException
异常offer
立刻返回false
put
会让调用的线程一直等待,直到方法执行成功
poll()
,take()
,获取队列的数据,如果队列为空,那么
poll
立刻返回nulltake
线程等待,直到获取到数据,或被中断
offer(E e, long timeout, TimeUnit unit)
,offer
另一种方法,当集合已满,如果等待时间小于等于0,那么会离开返回false,否则等到指定的时间
poll(long timeout, TimeUnit unit)
,如队列为空,当指定时间小于等于,立刻返回null,否则等待指定的时间
peek()
: 看一下队列当前的数据,如果队列为空,则立即返回null
源码分析(转载来的):
存储的结构: 数组
/** The queued items */ final Object[] items;
线程安全的掌控者: 锁
/** Main lock guarding all access */ final ReentrantLock lock;
add() || offer()
public boolean add(E e) { return super.add(e); } public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); // 一直等到获取锁 try { if (count == items.length) //假如当前容纳的元素个数已经等于数组长度,那么返回false return false; else { enqueue(e); // 将元素插入到队列中,返回true return true; } } finally { lock.unlock(); //释放锁 } }
put()
public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); //可中断的获取锁 try { while (count == items.length) //当线程从等待中被唤醒时,会比较当前队列是否已经满了 notFull.await(); //notFull = lock.newCondition 表示队列不满这种状况,假如现场在插入的时候 enqueue(e); //当前队列已经满了时,则需要等到这种情况的发生。 } finally { //可以看出这是一种阻塞式的插入方式 lock.unlock(); } }
poll()
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); //假如当前队列中的元素为空,返回null,否则返回出列的元素 } finally { lock.unlock(); } }
take()
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) //线程在刚进入 和 被唤醒时,会查看当前队列是否为空 notEmpty.await(); //notEmpty=lock.newCondition表示队列不为空的这种情况。假如一个线程进行take return dequeue(); //操作时,队列为空,则会一直等到到这种情况发生。 } finally { lock.unlock(); } }
peek()
public E peek() { final ReentrantLock lock = this.lock; lock.lock(); try { return itemAt(takeIndex); // null when queue is empty
// 实际上 itemAt 方法就是 return (E) items[i];
//也就是说 返回 数组中的第i个元素。
} finally {
lock.unlock();
}}
remove()
public E remove() { E x = poll(); if (x != null) return x; else throw new NoSuchElementException(); }
enqueue()
真正的入队操作
private void enqueue(E x) { //因为调用enqueue的方法都已经同步过了,这里就不需要在同步了 // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = x; //putIndex是下一个放至元素的坐标 if (++putIndex == items.length) //putIndex+1, 并且比较是否与数组长度相同,是的话,则从数组开头 putIndex = 0; //插入元素,这就是循环数组的奥秘了 count++; //当前元素总量+1 notEmpty.signal(); //给等到在数组非空的线程一个信号,唤醒他们。 }
dequeue()
出队操作
private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; //将要取出的元素指向null 表示这个元素已经取出去了 if (++takeIndex == items.length) //takeIndex +1,同样的假如已经取到了数组的末尾,那么就要重新开始取 takeIndex = 0; //这就是循环数组 count--; if (itrs != null) itrs.elementDequeued(); //这里实现就比较麻烦,下次单独出一个吧,可以看看源码 notFull.signal(); //同样 需要给 等待数组不满这种情况的线程一个信号,唤醒他们。 return x; }