学习ArrayBlockingQueue

首先了解ArrayBlockingQueue : 阻塞式数组队列 。 底层以数组的方式实现, 可以看做是 循环数组。

首先了解概念(转载来的):

  • 阻塞式集合(Blocking Collection): 这类集合一般在添加或删除数据,如果集合已满或为空时,则调用添加删除方法的线程会被阻塞,直接该方法可以成功执行
  • 非阻塞式集合(Non-Blocking Collection):这类集合一般在添加或删除数据,如果方法不能立即执行时,则会返回Null或抛出异常,但调用该方法的线程不会被阻塞.

add(E),offer(E),pub(E)都是这队列尾部加入元素E,如果队列不满,则加入成功,并立即返回.如果队列满了,那么

  • add会抛出IllegalStateException异常
  • offer立刻返回false
  • put会让调用的线程一直等待,直到方法执行成功

poll(),take(),获取队列的数据,如果队列为空,那么

  • poll 立刻返回null
  • take 线程等待,直到获取到数据,或被中断

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;
}

猜你喜欢

转载自blog.csdn.net/ssuperlg/article/details/79523812