NOTE

6.12 ArrayBlockingQueue

1. 是什么 使用Object数组实现的有界的阻塞队列 读读、读写、写写相互阻塞 2. 如何使用 2.1. 方法选择 方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出 ------------ --------- --------- ------- ------------------ 插入

Java创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 是什么

使用Object数组实现的有界的阻塞队列 读读、读写、写写相互阻塞

2. 如何使用

public class ArrayBlockingQueueTest
{
    public static void main(String[] args) throws InterruptedException
    {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        CountDownLatch latch = new CountDownLatch(2);

        new Thread(()->{
            for (int i = 0;;i++)
            {
                try
                {
                 	String data = "data" + i;
                    queue.put(data);
                    System.out.println("Producer放入消息:" + data);
                    TimeUnit.SECONDS.sleep(1);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        new Thread(()->{
            for (;;)
            {
                try
                {
                    System.out.println("Consumer获取消息:" + queue.take());
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        latch.await();

    }
}

2.1. 方法选择

方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用

3. 原理分析

3.1. uml

3.2. 构造方法

3.2.1. 底层使用数组+Lock+Condtion实现

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
	//底层是数组实现的
	final Object[] items;

	//take, poll, peek or remove等读方法,读取下一个元素的位置
	int takeIndex;
	//put, offer, or add等方法,写入下一个元素的位置
	int putIndex;

	//数组中实际元素的数量
	//当count==item.length()的时候说明数组已满
	int count;

	//一个锁说明读写互斥
	final ReentrantLock lock;
    //两个条件量
    private final Condition notEmpty;//用来唤醒读线程
	private final Condition notFull;//用来唤醒写线程

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

3.3. 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();
    }
}
  • 4行:加锁。一旦该写线程加锁其他读写线程都不能同时进来
  • 8-9行:如果数组已经满了,那么阻塞等待
  • 11行:没满则入队并唤醒读者

下面具体分析:

3.3.1. 加锁

//加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
    //...
} finally {
    lock.unlock();
}

3.3.2. 如果数组已经满了,那么等待

//如果数组已经满了,那么等待。直到读者取出元素后唤醒
while (count == items.length)
    notFull.await();

3.3.3. 没满则入队并唤醒读者

enqueue(e);
  • enqueue
private void enqueue(E x) {
    //把元素加入到队尾
    final Object[] items = this.items;
    items[putIndex] = x;
    //已插入到末尾,重置插入索引为0
    //这个数组是可以循环使用的,不需要扩容。
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    //插入后唤醒读者
    notEmpty.signal();
}

3.4. take【阻塞】

public E take() throws InterruptedException {
	//加锁
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
    	//如果数组为空,那么等待。写者加入元素后唤醒
        while (count == 0)
            notEmpty.await();
    	//出队
        return dequeue();
    } finally {
    	//释放锁
        lock.unlock();
    }
}
  • 3行:加锁。一旦该读线程加锁其他读写线程都不能同时进来
  • 6-8行:如果数组为空,那么等待
  • 10行:不为空则出队并唤醒写者

下面具体分析:

3.4.1. 加锁

//加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
 try {
	//...
} finally {
	//释放锁
    lock.unlock();
}

3.4.2. 如果数组为空,那么等待

//如果数组为空,那么等待。写者加入元素后唤醒
while (count == 0)
    notEmpty.await();

3.4.3. 不为空则出队并唤醒写者

  • dequeue
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    //获取最后一个元素并置为null
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    //已取到末尾,重置取值索引为0
     //这个数组是可以循环使用的,不需要扩容。
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    //出队后唤醒写者
    notFull.signal();
    return x;
}

3.5. offer【返回特殊值】

public boolean offer(E e) {
    checkNotNull(e);
    //加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	//已满,直接返回false
        if (count == items.length)
            return false;
        else {
        	//未满,加入队列同时唤醒读者
            enqueue(e);
            return true;
        }
    } finally {
    	//解锁
        lock.unlock();
    }
}

3.6. poll【返回特殊值】

public E poll() {
    final ReentrantLock lock = this.lock;
	//加锁
    lock.lock();
    try {
    	//长度为0直接返回null,否则出队并唤醒写者
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

3.7. add【抛出异常】

public boolean add(E e) {
	//简单调用AbstractQueue的add方法
    return super.add(e);
}

//AbstractQueue的add方法
public boolean add(E e) {
	//调用ArrayBlockingQueue的方offer法
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

3.8. remove【抛出异常】

public E remove() {
	//简单调用poll方法
    E x = poll();
    if (x != null)
        return x;
    else
    	//没有元素,抛出异常
        throw new NoSuchElementException();
}

3.9. element【抛出异常】

public E element() {
	//调用peek方法
    E x = peek();
    if (x != null)
        return x;
    else
    	//为空直接抛出异常
        throw new NoSuchElementException();
}

3.10. peek【返回特殊值】

public E peek() {
	//加锁
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		return itemAt(takeIndex); // null when queue is empty
	} finally {
		//解锁
		lock.unlock();
	}
}


@SuppressWarnings("unchecked")
final E itemAt(int i) {
	//直接返回数组中的第i个元素
    return (E) items[i];
}

4. 总结

底层使用数组实现,是个有界队列。 并且用了一个锁和两个condition。一个锁说明读写互斥,两个conditon说明读写相互唤醒

5. 参考