阻塞队列:
(查询BlockingQueue的API文档)
阻塞队列与Semaphore有些相似,但也不同,阻塞队列是一方存放数据,一方释放数据,Semaphore通常则是由同一方设置和释放信号量。
抛出异常 | 特殊值 | 阻塞 | 超时 | |
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
BlockingQueue 不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。
BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。
BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection
接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
BlockingQueue 实质上不 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。
以下是基于典型的生产者-使用者场景的一个用例。注意,BlockingQueue 可以安全地与多个生产者和多个使用者一起使用:
class Producer implements Runnable {
private final BlockingQueue queue;
Producer(BlockingQueue q) { queue = q; }
public void run() {
try {
while(true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Object produce() { ... }
}
class Consumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) { queue = q; }
public void run() {
try {
while(true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Object x) { ... }
}
class Setup {
void main() {
BlockingQueue q = new SomeQueueImplementation();
Producer p = new Producer(q);
Consumer c1 = new Consumer(q);
Consumer c2 = new Consumer(q);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
内存一致性效果:当存在其他并发 collection 时,将对象放入
BlockingQueue
之前的线程中的操作
happen-before 随后通过另一线程从
BlockingQueue
中访问或移除该元素的操作。
现在通过一个小案例演示:
用3个空间的队列来演示阻塞队列的功能和效果
package Multithreading;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueTest {
public static void main(String[] args) {
final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);
for (int i = 0; i < 2; i++) {//创建两个放数据的线程
new Thread() {
public void run() {
while (true) {
try {
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName()
+ "准备放数据!");
queue.put(1);
System.out.println(Thread.currentThread().getName()
+ "已经放了数据," + "队列目前有" + queue.size()
+ "个数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
new Thread() {//一个取数据的线程
public void run() {
while (true) {
try {
// 将此处的睡眠时间分别改为100和1000,观察运行结果
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()
+ "准备取数据!");
queue.take();
System.out.println(Thread.currentThread().getName()
+ "已经取走数据," + "队列目前有" + queue.size() + "个数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
运行的结果为:
如果把取线程的睡眠时间改短,变为100ms,则运行结果为:
阻塞队列还可以实现线程同步通知的功能,原理如图所示:
知识都带代码里,其实代码就可以说明一切。这里用一个曾经做过的题目当这个例子的案例,与传统线程通信的synchronized相对比一下。
/*
* 题目:在子程序里面运行10次,然后在主程序里运行100次,
* 然后再回到子程序里面运行10次,再回到主程序里面运行100次……如此反复运行50次。
* 写出程序。
*/
package Multithreading;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueThreadCommunication {
public static void main(String[] args) {
final Business business = new Business();
new Thread(new Runnable() {// 线程只是调用包装好的Business类
public void run() {
for (int i = 1; i <= 50; i++) {
business.sub(i);
}
}
}).start();
for (int i = 1; i <= 50; i++) {
business.main(i);
}
}
static class Business {
BlockingQueue<Integer> queue1 = new ArrayBlockingQueue<Integer>(1);
BlockingQueue<Integer> queue2 = new ArrayBlockingQueue<Integer>(1);
/**
* 看上去像静态代码快static{},但是这里肯定不能是静态代码块, 因为静态代码块不需要创建对象就可以,
* 而queue1和queue2是创建对象后的成员变量。
*/
{// 匿名构造方法,默认调用的方法,创建几个对象,就调用几次匿名构造方法
try {
queue2.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sub(int i) {
try {
queue1.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 1; j <= 10; j++) {
System.out.println("sub thread sequece of " + j + ",loop of "
+ i);
}
try {
queue2.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void main(int i) {
try {
queue2.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 1; j <= 100; j++) {
System.out.println("main thread sequece of " + j + ",loop of "
+ i);
}
try {
queue1.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}