一、概述:
ArrayBlockingQueue是基于数组的、定长(容量固定)的、线程安全的阻塞队列。
二、常用方法:
1、往队列中添加元素的方法有:add(E e)、put(E e)、offer(E e)、offer(E e, long timeout, TimeUnit unit)
2、移除队列中元素的方法有:remove(E e)、poll()、poll(long timeout, TimeUnit unit)、take()
1)、add(E e) 方法:ArrayBlockingQueue的 add() 方法 当队列满后,再添加元素就会抛出异常
2)、put(E e)方法:当队列满了之后, 会阻塞在 put() 方法那里 一直等待队列存在可用空间时才会继续执行往队列中添加元 素入股在等待的过程中被中断,则会抛出 InterruptedException
3)、offer(E e)方法:返回一个boolean类型的值,如果队列已满,则添加失败返回false,否则就添加成功返回true。不会因为队列已满无法添加进目标元素进队列里而抛出异常 true:代表添加元素成功。该方法不会阻塞。
4)、offer(E e, long timeout, TimeUnit unit)方法:返回一个boolean类型的值,如果队列已满,则添加失败返回false,否 则就添加成功返回true。不会因为队列已满无法添加进目标元素进队列里而抛出异常。该方法的后面两个参数分别是时间长度和时间单位,如果队列已满,该方法会等待指定的时间,如果在等待了指定的时间后队列依然没有空间,则抛弃该元素不让其入队(这里可以看作是设置阻塞指定时间,不成功则抛弃)。会抛出 InterruptedException
5)、remove() 方法: 该方法会返回一个boolean类型的值表示是否移除成功, 如果需要移除的元素在队列中不存在的话,不会 抛出异常。
6)、poll() 方法:获取并且移除队列的头元素,如果队列为空的话则返回null,不会抛出异常
7)、poll(long timeout, TimeUnit unit) 方法:获取并移除队列的头元素,可以控制阻塞时间,在队列中没有元素时会一直 等待(阻塞),直到队列中有新的可用元素,如果等待到指定时间队列中依然没有可用元素则返回 null。如果在等待时被中断会抛出InterruptedException
8)、take() 方法:该方法会阻塞,如果队列里没有元素时就会阻塞,知道对立中有元素时则取队列的头元素。如果在等待时中 断会抛出 InterruptedException
三、常用方法测试例子与简单分析:
1)、add(E e) 测试方法:
/**
* ArrayBlockingQueue的 add() 方法 当队列满后,再添加元素就会抛出异常
* @param queue 长度为 2 的 ArrayBlockingQueue
*/
private static void testAdd(BlockingQueue<Integer> queue) {
// 超过容量后会直接抛出异常,所以在使用 ArrayBlockingQueue 时需要注意和处理队列的长度问题
for (int i = 0; i < 10; i++) {
if (i == 2) {
System.out.println("第3个");
}
queue.add(i);
}
// 抛出空指针异常,队列不接受 null 元素
Integer integer = null;
queue.add(integer);
}
add(E e)方法实际上最终调用的是offer(E e) 在JDK源码里面做了元素的为空校验,以下为JDK1.8中的部分源码:
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();
}
}
从上面的代码中就可以看出,对参数 e 做了为空校验,使用重入锁保证线程安全,并且没有阻塞,如果队列已满,直接返回false,并且在add的实现中抛出 IllegalStateException("Queue full");。
2)、put(E e)方法测试方法:
/**
* ArrayBlockingQueue的 put() 方法 当队列满了之后,
* 会阻塞在 put() 方法那里
* 一直等待队列存在可用空间时才会继续执行往队列中添加元素
*
* @param queue BlockingQueue队列
*/
private static void testPut(BlockingQueue<Integer> queue) {
int i = 0;
Thread thread = new Thread("ArrayBlockingQueue队列 task() 方法测试线程") {
@Override
public void run() {
do {
try {
Thread.sleep(2000L);
System.out.println("获取队列中的元素" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (!queue.isEmpty());
}
};
thread.start();
for (i = 0; i < 10; i++) {
System.out.println("第" + i + "个");
try {
queue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如上代码中所示,该方法会抛出InterruptedException异常。当队列满了之后,这里利用重入锁使线程达到条件时等待,在构造ArrayBlockingQueue时就会构造两个条件一个是当队列满时,一个是队列中不存在元素时(notEmpty、notFull)。以下为JDK1.8中的部分代码:
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();
}
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();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
从以上代码中可以看出如果当条件满足时(队列中的元素个数与构建时指定的队列元素个数相等),则让线程等待,通过这种方式以达到阻塞队列的作用。同时也优先响应中断,而不是获取或冲入获取的。
3)、offer(E e)方法测试:
/**
* 返回一个boolean类型的值,如果队列已满,则添加失败返回false,否则就添加成功返回true。
* 不会因为队列已满无法将目标元素添加进队列里而抛出异常 true:代表添加元素成功
* false:代表添加元素失败
*/
private static void testOffer1(BlockingQueue<Integer> queue) {
for (int i = 0; i < 10; i++) {
boolean flag = queue.offer(i);
System.out.println("第" + i + "个" + flag);
}
}
以下是源码:
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();
}
}
/**
* Inserts element at current put position, advances, and signals.
* Call only when holding lock.
*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
如需要了解原理的话,可以自行查看源码,下面的就不再分析和贴出源码了。
4)、offer(E e, long timeout, TimeUnit unit)方法:
/**
* 返回一个boolean类型的值,如果队列已满,则添加失败返回false,否则就添加成功返回true。
* 不会因为队列已满无法添加进目标元素进队列里而抛出异常
* 该方法的后面两个参数分别是时间长度和时间单位,如果队列已满,
* 如下代码所示:该方法会等待5秒,如果5秒后队列依然没有空间,则抛弃该元素不让其入队(这里可以看作是设置阻塞5秒,不成功则抛弃)
* true:代表添加元素成功 false:代表添加元素失败
*/
private static void testOffer2(BlockingQueue<Integer> queue) {
try {
for (int i = 0; i < 10; i++) {
boolean flag = queue.offer(i, 5, TimeUnit.SECONDS);
System.out.println("第" + i + "个" + flag);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
5)、
remove() 方法:
/**
* ArrayBlockingQueue的 remove() 方法,
* 该方法会返回一个boolean类型的值表示是否移除成功,
* 如果需要移除的元素在队列中不存在的话,不会抛出异常。
*/
private static void testRemove(BlockingQueue<Integer> queue) {
for (int i = 0; i < 5; i++) {
System.out.println(i);
boolean flag = queue.remove(i);
System.out.println(queue.isEmpty() +"------"+ flag);
}
}
6)、
poll() 方法:
/**
* ArrayBlockingQueue的 poll() 方法, 获取并且移除队列的头元素,如果队列为空的话则返回null,不会抛出异常
*/
private static void testPoll(BlockingQueue<Integer> queue) {
for (int i = 0; i < 3; i++) {
// 这里的变量类型不可以使用 int ,如果返回的是 null 则会抛出空指针异常
Integer result = queue.poll();
System.out.println(result);
}
}
7)、
poll(
long
timeout
,
TimeUnit
unit
) 方法:
/**
* ArrayBlockingQueue的 poll() 方法 获取并队列的头元素,可以控制阻塞时间,
* 在队列中没有元素时会一直等待(阻塞),直到队列中有新的可用元素,
* 如果等待到指定时间队列中依然没有可用元素则返回 null。如果在等待时被中断会抛出InterruptedException
* @param queue 长度为2的ArrayBlockingQueue
*/
private static void testPoll2 (BlockingQueue<Integer> queue) {
try {
for (int i = 0; i < 3; i++) {
Integer result = queue.poll(2L, TimeUnit.SECONDS);
System.out.println(result);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
8)、
take() 方法:
/**
* ArrayBlockingQueue的 take() 方法, 该方法会阻塞,
* 如果队列里没有元素时就会阻塞,知道对立中有元素时则取队列的头元素
* 如果在等待时中断会抛出 InterruptedException
* @param queue 长度为2的ArrayBlockingQueue
*/
private static void testTake(BlockingQueue<Integer> queue) {
try {
for (int i = 0; i < 3; i++) {
Integer result = queue.take();
System.out.println(result);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}