文章预览:
缓冲队列 BlockingQueue
一、BlockingQueue缓存队列简单概述
1、什么是阻塞队列BlockingQueue
BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种:
- 当队列满了的时候进行入队列操作
- 当队列空了的时候进行出队列操作
因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空队列进行出队列操作时,它将会被阻塞,除非有另一个线程进行了入队列操作。
阻塞队列主要用在生产者/消费者的场景,当BlockingQueue中的add方法去放入商品时,如果队列中的空间已满,则无法放入,会抛出异常;反之,当BlockingQueue中的remove方法去取出商品时,如果队列中的空间为空,则无法取出,也会抛出异常。
下面展示下面这幅图展示了一个线程生产、一个线程消费的场景:
2、BlockingQueue接口中的部分方法
接口方法 | 方法简单介绍 |
---|---|
add(E e) | 在不违反容量限制的情况下,可立即将指定元素插入此队列,成功返回true,当无可用空间时候,返回IllegalStateException异常。 |
offer(E e) | 在不违反容量限制的情况下,可立即将指定元素插入此队列,成功返回true,当无可用空间时候,返回false。 |
put(E e) | 直接在队列中插入元素,当无可用空间时候,阻塞等待。 |
offer(E e, long time, timeunit unit) | 将给定元素在给定的时间内设置到队列中,如果设置成功返回true, 否则返回false。 |
E take() | 获取并移除队列头部的元素,无元素时候阻塞等待。 |
E poll( long time, timeunit unit) | 获取并移除队列头部的元素,无元素时候阻塞等待指定时间。 |
3、BlockingQueue的父类与部分实现类
ArrayBlockingQueue和SynchronousQueue都是BlockingQueue的实现类。下面主要以这两个类为例子进一步解析阻塞队列。
二、ArrayBlockingQueue类
1、什么是ArrayBlockingQueue
ArrayBlockingQueue是一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。
这是一个经典的“有界缓冲区”,其中固定大小的数组保存由生产者插入的元素并由消费者提取。 创建后,容量无法更改。使用put方法将新元素放入满队列时会导致线程阻塞,使用take方法从空队列中取元素时也会导致线程阻塞。
此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,是非公平的。即不保证等待时间最长的队列最优先能够访问队列。
2、ArrayBlockingQueue中的四组API
抛出异常 | 不抛出异常,有返回值 | 阻塞等待 | 超时等待 | |
---|---|---|---|---|
添加元素方法 | add(E e) | offer(E e) | put(E e) | offer(E e, long timeout, TimeUnit unit) |
移除元素方法 | remove(Object o) | poll() | take() | poll(long timeout, TimeUnit unit) |
检测队首元素 | element() | peek() | - | - |
3、代码实现
抛出异常
public class BlockingQueueTest {
public static void main(String[] args) {
test01();
}
//BlockingQueue阻塞队列会抛出异常
public static void test01() {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//获取首元素 java.util.NoSuchElementException
//blockingQueue.element();
blockingQueue.add("aa");
blockingQueue.add("bb");
blockingQueue.add("cc");
//会抛出 java.lang.IllegalStateException: Queue full 异常
//blockingQueue.add("dd");
System.out.println("===========================");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//会抛出 java.util.NoSuchElementException 异常
//System.out.println(blockingQueue.remove());
}
}
不抛出异常,有返回值
public class BlockingQueueTest {
public static void main(String[] args) {
test02();
}
//BlockingQueue阻塞队列不会抛出异常,有返回值
public static void test02() {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//获取首元素
//System.out.println(blockingQueue.peek()); 返回 null
System.out.println(blockingQueue.offer("aa"));
System.out.println(blockingQueue.offer("bb"));
System.out.println(blockingQueue.offer("cc"));
//System.out.println(blockingQueue.offer("dd")); 返回 false
System.out.println("===========================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//System.out.println(blockingQueue.poll()); 返回 null
}
}
阻塞等待
public class BlockingQueueTest {
public static void main(String[] args) {
test03();
}
//BlockingQueue阻塞队列会阻塞等待
public static void test03() {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
try {
blockingQueue.put("aa");
blockingQueue.put("bb");
blockingQueue.put("cc");
//会一直等待直到队列内有空间可以放入数据
//blockingQueue.put("dd");
System.out.println("===========================");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//会一直等待直到队列内有数据
//System.out.println(blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
超时等待
public class BlockingQueueTest {
public static void main(String[] args) {
test04();
}
//BlockingQueue阻塞队列会超时等待
public static void test04() {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
try {
blockingQueue.offer("aa");
blockingQueue.offer("bb");
blockingQueue.offer("cc");
//会等待两秒,如果队列一直没有空间,就退出
//blockingQueue.offer("dd",1L, TimeUnit.SECONDS);
System.out.println("===================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//会等待两秒,如果队列一直为空,就退出
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三、SynchronousQueue类
1、什么是SynchronousQueue(同步队列)
1. A blocking queue其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量, 甚至没有一个容量。
2. 你不能peek在同步队列,因为一个元素,当您尝试删除它才存在; 您无法插入元素(使用任何方法), 除非另有线程正在尝试删除它;
3. 你不能迭代,因为没有什么可以迭代。
4. 队列的头部是第一个排队的插入线程尝试添加到队列中的元素; 如果没有这样排队的线程,那么没有元素可用于删除,并且poll()将返回null 。
5. 为了其他Collection方法(例如contains )的目的,SynchronousQueue充当空集合。 此队列不允许null元素。
2、使用代码去解释上述的内容
SynchronousQueue(同步队列)的插入操作必须对应一个线程中的删除操作,如果只在单线程中去插入元素,则会导致当前的线程陷入阻塞等待,反之相同。
简单图解:
代码实现:
public class SynchronousQueueTest02 {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "放入一个元素");
blockingQueue.put("宝马汽车");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "取出元素:" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
/*
输出结果:
Thread-0放入一个元素
Thread-1取出元素:宝马汽车
*/
执行程序时,Thread-0线程抢占到了cup资源,在控制台输出”Thread-0放入一个元素“,并执行blockingQueue.put(“宝马汽车”)代码,由于此时在Thread-1线程中还未执行blockingQueue.take()代码,所以Thread-0线程陷入阻塞;当Thread-1线程执行到 blockingQueue.take()代码时会唤醒Thread-0线程继续执行,Thread-0线程往队列中放入元素,此时Thread-1线程就能移除队列中的元素。反之相同。
不能在SynchronousQueue(同步队列)中使用peek()方法,因为SynchronousQueue内部不像ArrayBlockingQueue或LinkedListBlockingQueue,它并没有数据缓存空间,不能调用peek()方法来看队列中是否有数据元素,因为数据元素只有尝试取出的时候才可能会存在,不取走时只想偷窥一下是不行的。
使用peek()方法返回的永远是null。
队列头元素是第一个排队要插入数据的线程,而不是要交换的数据。数据是在配对的生产者和消费者线程之间直接传递的,并不会将数据缓冲数据到队列中。可以这样来理解:生产者和消费者互相等待对方,握手,然后一起离开。
代码实现:
public class SynchronousQueueTest02 {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "放入一个元素");
blockingQueue.put("宝马汽车");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "取出元素:" + blockingQueue.peek());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
/*
输出结果:
Thread-0放入一个元素
Thread-1取出元素:null
程序会一直运行,因为Thread-0生产者线程并没有匹配到一个消费者线程去取数据元素,所以会陷入阻塞状态。
*/
SynchronousQueue(同步队列)不能够去迭代,因为他的内部没有容量。
如果在SynchronousQueue(同步队列)中没有一个尝试去插入数据元素的线程,那么poll()将返回null 。如上面提到的poll()方法不抛出异常,有返回值。
代码实现:
public class SynchronousQueueTest02 {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "取出元素:" + blockingQueue.poll());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
/*
输出结果:
Thread-0取出元素:null
*/
对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空集合。此队列不允许 null 元素。