BlockingQueue是Queue子接口。所以它实现有队列的基本特征:
Public interface BlockingQueue extends Queue
在最初利用Queue实现生产者与消费者模型时发现一个问题:所有的消费者可能不是一个个轮流操作,
而是有可能某一个消费者会长期进行消费处理。
【 阻塞队列 】
➤ BlockingQueue通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。
➤ 一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界
点。也就是说,它是有限的。如果该阻塞队列达到了其临界点,负责生产的线程将会在
往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中
拿走一个对象。
BlockingQueue也是一个处理接口,如果要想操作BlockingQueue也需要使用它的一系列子类。
【 BlockingQueue基础子类 】
对于阻塞队列而言最基础的两个实现子类:数组的队列、链表的队列。
范例:使用BlockingQueue实现一个生产者与消费者模型
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
// 允许保存5个数据队列
BlockingQueue queue =
new ArrayBlockingQueue(5);
for (int x = 0; x < 3; x++) {
new Thread(() -> {
for (int y = 0; y < 5; y++) {
try {
TimeUnit.SECONDS.sleep(1);
String str = "【生产数据{"
+ Thread.currentThread().getName()
+ "}】y = " + y ;
queue.put(str); // 会进入到生产的阻塞状态
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者-" + x).start();
}
for (int x = 0; x < 5; x++) {
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
// 队列内容为空了
if (queue.isEmpty()) {
break; // 结束循环
}
System.out.println("【消费数据{"
+ Thread.currentThread().getName()
+ "}】" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者-" + x).start();
}
}
}
除了数组之外也可以使用链表来进行操作:LinkedBlockingQueue。在使用这个类进行BlockQueue接口对象实例化的时候,
如果没有设置容量,则容量为”Integer.MAX_VALUE”。
范例:修改为链表实现
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
// 允许保存5个数据队列
BlockingQueue queue
= new LinkedBlockingQueue(5);
for (int x = 0; x < 3; x++) {
new Thread(() -> {
for (int y = 0; y < 5; y++) {
try {
TimeUnit.SECONDS.sleep(1);
String str = "【生产数据{"
+ Thread.currentThread().getName()
+ "}】y = " + y ;
queue.put(str); // 会进入到生产的阻塞状态
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者-" + x).start();
}
for (int x = 0; x < 5; x++) {
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
if (queue.isEmpty()) { // 队列内容为空了
break; // 结束循环
}
System.out.println("【消费数据{"
+ Thread.currentThread().getName()
+ "}】" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者-" + x).start();
}
}
}
链表是通过索引来进行弹出数据的,而链表只需要弹出第一个元素即可。
范例:采用优先级的PriorityBlockingQueue来实现数据操作采用了Comparable接口来进行处理实现。
import java.util.concurrent.BlockingQueue; PriorityBlockingQueue; TimeUnit;
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
// 允许保存5个数据队列
BlockingQueue queue =
new PriorityBlockingQueue(5);
for (int x = 0; x < 3; x++) {
new Thread(() -> {
for (int y = 0; y < 5; y++) {
try {
TimeUnit.SECONDS.sleep(1);
String str = "【生产数据{"
+ Thread.currentThread().getName()
+ "}】y = " + y ;
queue.put(str); // 会进入到生产的阻塞状态
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者-" + x).start();
}
for (int x = 0; x < 5; x++) {
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
if (queue.isEmpty()) { // 队列内容为空了
break; // 结束循环
}
System.out.println("【消费数据{"
+ Thread.currentThread().getName()
+ "}】" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者-" + x).start();
} } }
对于使用拿一个具体的子类完全是由你具体的开发环境来决定,需要至少知道BlockingQueue这个阻塞队列核心就是提供有同步和队列的功能。
【 同步队列:SynchronousQueue 】
之前使用BlockingQueue每一次都可以保存多个数据对象信息,但是有些时候只能够允许你保存一个数据的信息,这种情况下就要使用SynchronousQueue子类完成。
范例:使用同步队列进行处理
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
// 允许保存5个数据队列
BlockingQueue queue =
new SynchronousQueue();
for (int x = 0; x < 3; x++) {
new Thread(() -> {
for (int y = 0; y < 5; y++) {
try {
TimeUnit.SECONDS.sleep(1);
String str = "【生产数据{"
+ Thread.currentThread().getName()
+ "}】y = " + y ;
queue.put(str); // 会进入到生产的阻塞状态
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者-" + x).start();
}
for (int x = 0; x < 5; x++) {
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("【消费数据{"
+ Thread.currentThread().getName()
+ "}】" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者-" + x).start();
} } }
现在不关心有多少个生产者和消费者都采用一个接一个的形式执行。
但是现在有些操作者希望可以按照前后各自处理操作。
➢ 一个BlockingDeque线程在双端队列的两端都可以插入和提取元素。
➣ 一个线程生产元素,并把它们插入到队列的任意一端。如果双端队列已满,
插入线程将被阻塞,直到一个移除线程从该队列中移除了一个元素。如果双端队列为空,
移除线程将被阻塞,直到一个插入线程向该队列插入了一个新元素。
范例:观察双端阻塞队列的基本使用
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
BlockingDeque deque =
new LinkedBlockingDeque();
for (int x = 0; x < 3; x++) {
new Thread(() -> {
for (int y = 0; y < 5; y++) {
try {
TimeUnit.SECONDS.sleep(1);
String str = null ;
if (y % 2 == 0) {
str = "【[FIRST]生产数据{"
+ Thread.currentThread().getName()
+ "}】y = " + y ;
deque.addFirst(str);
} else {
str = "【[LAST]生产数据{"
+ Thread.currentThread().getName()
+ "}】y = " + y ;
deque.addLast(str);
}
// System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者-" + x).start();
}
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("【FIRST-消费数据{"
+ Thread.currentThread().getName()
+ "}】" + deque.takeFirst());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者FIRST").start();
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("【LAST-消费数据{"
+ Thread.currentThread().getName()
+ "}】" + deque.takeLast());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者LAST").start();
}
}
生产者和消费者模型的实现方案很多。对于双端阻塞队列一定要清楚它本身还是一个队列,如果现在first已经拽干净了,
那么将继续拽last,就会有可能出现first消费last的情况。
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class MLDNTestDemo {
public static void main(String[] args) throws Exception {
// 利用链表来实现
BlockingDeque deque =
new LinkedBlockingDeque();
deque.addFirst("Hello-First");
deque.addFirst("World-First");
deque.addLast("Hello-Last");
deque.addLast("World-Last");
System.out.println(deque.takeFirst());
System.out.println(deque.takeFirst());
System.out.println(deque.takeFirst());
System.out.println(deque.takeFirst());
}
}
如果有一端出现了阻塞,那么至少另外一端还可以供你使用。