二十九、阻塞队列之ArrayBlockingQueue

55 阅读5分钟

阻塞队列之ArrayBlockingQueue

ArrayBlockingQueue是有界的阻塞队列,创建时需要声明队列容量。其内部只声明了一把锁,说明生产者和消费者是共用这把锁的,那么同一时刻,就只能生产数据或者消费数据了,效率比那种生产者和消费者各自有一把锁的阻塞队列要低。

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

ArrayBlockingQueue的基本属性

// 存放元素的数组
final Object[] items;

// 取元素的索引
int takeIndex;

// 存元素的索引
int putIndex;

// 元素个数
int count;

// 生产者和消费者共用一个锁
final ReentrantLock lock;

// 消费者的Condition
private final Condition notEmpty;

// 生产者的Condition
private final Condition notFull;

ArrayBlockingQueue存放元素的方法

add方法

public boolean add(E e) {
	if (offer(e))
		// 存放成功,返回true
		return true;
	else
		// 存放失败,抛异常
		throw new IllegalStateException("Queue full");
}

offer非阻塞方法

  1. 判断入参是否为空,如果为空抛出异常
  2. 加锁
  3. 判断队列是否已满,如果满了返回false
  4. 队列未满,将数据放到putIndex指定位置
  5. 将count+1,并且将putIndex+1
  6. 判断putIndex值是否超出数组索引范围,如果超出,putIndex设置为0
  7. 唤醒消费者
  8. 释放锁
public boolean offer(E e) {
	// 检查要存放的元素是否为null,如果是,抛异常
	checkNotNull(e);
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		if (count == items.length)
			// 队列已满,返回false
			return false;
		else {
			// 队列未满,存放元素
			enqueue(e);
			return true;
		}
	} finally {
		lock.unlock();
	}
}

private void enqueue(E x) {
	final Object[] items = this.items;
	items[putIndex] = x;
	if (++putIndex == items.length)
		// 当队列已经满了,存放索引就会移动到队列头部
		putIndex = 0;
	count++;
	// 即时唤醒消费者
	notEmpty.signal();
}

offer阻塞方法

  1. 判断入参是否为空,如果为空抛出异常
  2. 加锁,加锁的方法可以响应中断
  3. 这里使用while循环,判断队列是否已满
  4. 如果队列已满,判断方法的等待时间是否已过,如果已过,返回false
  5. 方法的等待时间未过,线程阻塞一段时间,并且释放锁
  6. 线程被唤醒,尝试获取锁,获取锁后,重复执行3、4、5步
  7. 如果队列未满,将数据放入队列中
  8. 将putIndex值和count值都+1
  9. 判断putIndex值是否超出数组索引范围,如果超出,putIndex设置为0
  10. 唤醒消费者
  11. 释放锁
public boolean offer(E e, long timeout, TimeUnit unit)
	throws InterruptedException {
	// 如果存入的元素为null,抛出异常
	checkNotNull(e);
	long nanos = unit.toNanos(timeout);
	final ReentrantLock lock = this.lock;
	// lockInterruptibly加锁方法可以响应中断
	lock.lockInterruptibly();
	try {
		while (count == items.length) {
			if (nanos <= 0)
				return false;
			// 当前线程释放锁资源,并且挂起一段时间
			// 如果在这段时间中被唤醒,当前线程会竞争锁资源
			// 如果成功拿到锁资源,往队列中存放元素
			// 如果是时间到了被唤醒,也会竞争锁资源
			// 如果成功拿到锁资源,往队列中存放元素
			nanos = notFull.awaitNanos(nanos);
		}
		enqueue(e);
		return true;
	} finally {
		lock.unlock();
	}
}

put方法

  1. 判断入参是否为空,如果为空抛出异常
  2. 加锁,加锁的方法可以响应中断
  3. 这里使用while循环,判断队列是否已满
  4. 如果队列已满,线程阻塞等待,并且释放锁
  5. 线程被唤醒,尝试获取锁,获取锁后,重复执行3、4步
  6. 如果队列未满,将数据放入队列中
  7. 将putIndex值和count值都+1
  8. 判断putIndex值是否超出数组索引范围,如果超出,putIndex设置为0
  9. 唤醒消费者
  10. 释放锁
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();
	}
}

ArrayBlockingQueue获取元素的方法

remove无参方法

public E remove() {
	E x = poll();
	if (x != null)
		return x;
	else
		throw new NoSuchElementException();
}

poll无参方法

  1. 加锁
  2. 判断队列是否为空,如果为空,返回null
  3. 队列不为空,获取takeIndex索引位置的数据
  4. 将队列的takeIndex索引位置设置为null
  5. 将takeIndex+1,并且count-1,判断takeIndex值是否超过队列索引范围
  6. 如果超过,将takeIndex设置为0
  7. 唤醒生产者线程
  8. 释放锁
public E poll() {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		// 队列中没有元素,返回null
		return (count == 0) ? null : dequeue();
	} finally {
		lock.unlock();
	}
}

private E dequeue() {
	final Object[] items = this.items;
	// 根据takeIndex取出队列中的元素
	E x = (E) items[takeIndex];
	// 将takeIndex位置的元素设置为null
	items[takeIndex] = null;
	if (++takeIndex == items.length)
		// takeIndex超过队列长度,设置为0
		takeIndex = 0;
	count--;
	// itrs是迭代器内容,先跳过不分析
	if (itrs != null)
		itrs.elementDequeued();
	// 唤醒生产者端被挂起的线程
	notFull.signal();
	return x;
}

poll有参方法

  1. 加锁,这里加锁的方法可以响应中断
  2. while循环,判断队列是否为空,如果为空,判断方法等待时间是否已过
  3. 如果已过,返回null
  4. 如果未过,线程阻塞一段时间,并且释放锁
  5. 线程被唤醒,尝试获取锁,获取到锁后,重复2、3、4步
  6. 获取takeIndex索引位置的数据
  7. 唤醒生产者线程
  8. 释放锁
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
	long nanos = unit.toNanos(timeout);
	final ReentrantLock lock = this.lock;
	lock.lockInterruptibly();
	try {
		while (count == 0) {
			if (nanos <= 0)
				// 剩余时间<=0,直接返回null
				return null;
			// 当前线程释放锁资源,并且挂起一段时间
			nanos = notEmpty.awaitNanos(nanos);
		}
		return dequeue();
	} finally {
		lock.unlock();
	}
}

take方法

  1. 加锁,这里加锁的方法可以响应中断
  2. 判断队列是否为空,如果为空,线程阻塞,并且释放锁
  3. 线程被唤醒,尝试获取锁,获取到锁后,重复2步
  4. 获取takeIndex索引位置的数据
  5. 唤醒生产者线程
  6. 释放锁
public E take() throws InterruptedException {
	final ReentrantLock lock = this.lock;
	lock.lockInterruptibly();
	try {
		while (count == 0)
			notEmpty.await();
		return dequeue();
	} finally {
		lock.unlock();
	}
}