概述
阻塞队列有两个特性:
阻塞:当队列为空时,会阻塞队列弹出操作,直到队列不为空。当队列满了时,会阻塞入队操作,直到队列不满。
队列:FIFO,先进先出。
接口:java.util.concurrent.BlockingQueue。
实现接口:Collection , Iterable , Queue
已知常用实现类:
- ArrayBlockingQueue :底层为数组的阻塞队列。
- LinkedBlockingDeque :底层为链表的双端阻塞队列。
- LinkedBlockingQueue :底层为链表的单端阻塞队列。
- SynchronousQueue :同步队列
BlockingQueue的四组API
这四组API是添加元素、删除元素、获取队首元素的四组API,每组以是否抛出异常,是否阻塞等待为维度进行切分。
先上总表格:
方式 | 抛出异常 | 有返回值且不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add () | offer() | put() | offer() |
移除 | remove() | poll() | take() | poll() |
获取队首元素 | element() | peek() | - | - |
测试会抛出异常的方法
public static void testThrowsExceptionApi(){
//创建一个阻塞队列,容量为3
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add(1);
blockingQueue.add(2);
blockingQueue.add(3);
//单单开放下面代码,会在一个容量为3的队列中加入第4个元素,抛出队列已满的异常
//blockingQueue.add(4);
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//单单开放下面代码,在空队列中移除元素,抛出异常
//System.out.println(blockingQueue.remove());
//单单开放下面代码,在空队列中获取队首元素,抛出异常
//System.out.println(blockingQueue.element());
}
正常情况:
往满队列中添加元素:
往空队列中移除元素:
往空队列获取队首元素:
测试不抛出异常且有返回值的Api
public static void testNonThrowsExceptionApi(){
//创建一个阻塞队列,容量为3
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
//添加元素,成功返回true,否则返回false
System.out.println(blockingQueue.offer(1));
System.out.println(blockingQueue.offer(2));
System.out.println(blockingQueue.offer(3));
System.out.println(blockingQueue.offer(4));
//移除元素,成功返回元素值,失败返回null
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//获取队首元素,成功返回元素值,失败返回null
System.out.println(blockingQueue.peek());
}
结果:都没有抛出异常。
测试阻塞等待Api
public static void testBlockWaitApi() throws InterruptedException {
//创建一个阻塞队列,容量为3
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put(1);
blockingQueue.put(2);
blockingQueue.put(3);
//如果打开下面代码。因为此时队列已满,所以会一直阻塞在这里,直到队列不满为止
//blockingQueue.put(4);
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//如果打开下面代码。因为此时队列为空,所以会一直阻塞在这里,直到队列不为空为止
//System.out.println(blockingQueue.take());
}
结果:
添加时阻塞:会一直阻塞住线程
移除时阻塞:尝试获取空数组元素时,被阻塞住了。
没有阻塞等待的获取队首Api。
测试阻塞超时等待
public static void testBlockWaitApiWithTimeout(){
//创建一个阻塞队列,容量为3
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
try {
System.out.println(blockingQueue.offer(1, 3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(blockingQueue.offer(2, 3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(blockingQueue.offer(3, 3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
//如果打开下面代码。因为此时队列已满,所以会一直阻塞在这里,直到队列不满或者三秒超时返回false
System.out.println(blockingQueue.offer(4, 3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果打开下面代码。因为此时队列为空,所以会一直阻塞在这里,直到队列不为空或者等待超时3秒返回null为止
try {
System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果:
超时等待没有对应的获取队首元素API。
同步队列SynchronousQueue
同步队列添加元素后,就会阻塞当前线程,然后等待其他线程移除该元素后,才会唤醒那个线程。可以使用它来做一对一的线程间通信。
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println("添加元素1前");
synchronousQueue.put(1);
System.out.println("添加元素1后");
System.out.println("添加了元素2前");
synchronousQueue.put(2);
System.out.println("添加元素2后");
System.out.println("添加元素3前");
synchronousQueue.put(3);
System.out.println("添加元素3后");
}catch (InterruptedException e){
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("释放了元素" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println("释放了元素" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println("释放了元素" + synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
由结果可以看出,队列添加元素之后,就会阻塞住线程,然后等待另一个线程移除该元素,该线程也会被唤醒。所以先打印添加元素前,然后添加元素,之后线程阻塞,所以没有先打印添加元素后,而是先打印释放元素,然后添加线程被唤醒,就打印添加元素后。