可以采用队列来进行中间的缓冲处理,好处:生产者可以一直不停歇的生产数据。
BlockingQueue是Queue子接口。所以它实现有队列的基本特征:
Public interface BlockingQueue<E> extends Queue<E>在最初利用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<String> queue = new ArrayBlockingQueue<String>(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<String> queue = new LinkedBlockingQueue<String>(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<String> queue = new PriorityBlockingQueue<String>(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<String> queue = new SynchronousQueue<String>(); 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 】
BlockingQueue主要特征是只能够从一方面获取数据,也就是说它按照了一个队列的形式采用了FIFO的处理完成,
但是现在有些操作者希望可以按照前后各自处理操作。
➢ 一个BlockingDeque线程在双端队列的两端都可以插入和提取元素。
➣ 一个线程生产元素,并把它们插入到队列的任意一端。如果双端队列已满,
插入线程将被阻塞,直到一个移除线程从该队列中移除了一个元素。如果双端队列为空,
移除线程将被阻塞,直到一个插入线程向该队列插入了一个新元素。
范例:观察双端阻塞队列的基本使用
public class MLDNTestDemo { public static void main(String[] args) throws Exception { BlockingDeque<String> deque = new LinkedBlockingDeque<String>(); 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<String> deque = new LinkedBlockingDeque<String>(); 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()); } }如果有一端出现了阻塞,那么至少另外一端还可以供你使用。