JavaRush /Java 博客 /Random-ZH /Java中的SynchronousQueue示例——解决Producer Consumer问题
profeg
第 18 级

Java中的SynchronousQueue示例——解决Producer Consumer问题

已在 Random-ZH 群组中发布
Java中的SynchronousQueue示例——解决Producer Consumer问题
SynchronousQueue是一种特殊类型的BlockingQueue,其中每个插入操作都必须等待另一个线程中相应的删除命令,反之亦然。当您在 SynchronousQueue 上调用put() 方法时,它会阻塞,直到另一个线程从中获取该元素。因此,如果另一个线程尝试从中删除一个元素,但该元素不存在,则该线程会阻塞,直到另一个线程将该元素放入队列中。你可以把 SynchronousQueue 想象成一个拿着奥运火炬奔跑的运动员(线程),他拿着火炬(正在传递的对象)奔跑,并将其传递给另一边等待的另一位运动员。如果你注意这个名字,你就会明白 SynchronousQueue 这么命名是有原因的;它同步地将数据传输到另一个线程;它等待某人获取数据,而不是仅仅将其放入并退出(异步操作)。如果您熟悉 CSP 和 Ada,那么您就会知道同步队列类似于线程的会议。它们非常适合控制传输结构,其中一个线程中运行的对象必须与另一个线程中的对象同步,以便向其传递一些信息、事件或任务。在之前的多线程编程教程中,我们学习了如何使用wait和notify以及BlockingQueue方法解决生产者-消费者问题。现在我们将学习如何使用 SynchronousQueue 应用生产者-消费者模式。此类还支持对生产者线程和消费者线程的等待进行排序的公平行为。默认情况下,不保证此顺序。但是,使用公平属性创建的队列可保证 FIFO (先进先出)队列 中线程的访问。
Java 中使用 SynchronousQueue 的生产者/消费者。
Java中的SynchronousQueue示例 - 解决Producer Consumer问题 - 1正如我上面所说,对于理解任何编程语言中的线程间通信 来说,没有什么比生产者-消费者问题更好的了。在此问题中,一个线程充当生成事件和任务的生产者,另一个线程充当其消费者。共享缓冲区用于将数据从生产者传输到消费者。在极端情况下解决这个问题会很困难,例如,当制造商被迫等待时,因为...... 缓冲区已满或消费者被迫等待,因为 缓冲区为空。这个问题很容易解决,因为... 阻塞队列不仅提供了存储数据的缓冲区,还提供了流量控制,如果缓冲区已满,则阻塞调用 put() 方法的线程(Producer),如果缓冲区已满,则阻塞调用 take() 方法的线程(Consumer)。缓冲区为空。现在我们将使用 SynchronousQueue 来解决同样的问题,SynchronousQueue 是一种特殊的零容量并行集合。在下面的示例中,我们有两个线程,分别称为PRODUCERCONSUMER(始终为线程命名,这是一种非常好的多线程编程风格)。第一个线程发布游戏中的分数,第二个线程使用它。游戏中的分数无非就是一个String类型的对象。但是如果您使用不同的类型运行该程序,您将不会注意到任何差异。要了解 SynchronousQueue 是如何工作的,以及如何解决生产者-消费者问题,您需要:要么在 Eclipse 环境中运行程序进行调试(debug),要么干脆通过注释掉consumer.start()来启动生产者线程;如果消费者线程没有运行,那么生产者线程将被阻塞在queue.put(event); 如果运行,您将无法看到生产者 [PRODUCER] 发布 :FOUR 事件。发生这种情况是因为 SynchronousQueue 的特定行为,它确保发布数据的线程将阻塞,直到另一个线程获取数据,反之亦然。您可以通过注释掉 Producer.start(); 来测试其余代码。并仅启动消费者线程。 如果仔细研究程序的输出内容,您会发现输出的顺序是相反的。看起来[CONSUMER]线程在[PRODUCER]线程生成数据 之前获取了数据。这是因为 SynchronousQueue 默认情况下不保证排队。但它具有公平规则,以 FIFO 顺序设置对线程的访问。您可以通过将 true 传递给重载的SynchronousQueue 构造函数来启用这些规则,如下所示: import java.util.concurrent.SynchronousQueue; /** * Java Program to solve Producer Consumer problem using SynchronousQueue. A * call to put() will block until there is a corresponding thread to take() that * element. * * @author Javin Paul */ public class SynchronousQueueDemo{ public static void main(String args[]) { final SynchronousQueue queue = new SynchronousQueue (); Thread producer = new Thread("PRODUCER") { public void run() { String event = "FOUR"; try { queue.put(event); // thread will block here System.out.printf("[%s] published event : %s %n", Thread .currentThread() .getName(), event); } catch (InterruptedException e) { e.printStackTrace(); } } }; producer.start(); // starting publisher thread Thread consumer = new Thread("CONSUMER") { public void run() { try { String event = queue.take(); // thread will block here System.out.printf("[%s] consumed event : %s %n", Thread .currentThread() .getName(), event); } catch (InterruptedException e) { e.printStackTrace(); } } }; consumer.start(); // starting consumer thread } } Output: [CONSUMER] consumed event : FOUR [PRODUCER] published event : FOUR new SynchronousQueue(boolean fair).
关于 Java 中的 SynchronousQueue,您需要记住什么。

下面是 Java 中这种特殊类型的阻塞队列的一些重要属性。以同步方式将数据从一个线程传递到另一个线程非常有用。该队列没有容量并且被阻塞,直到另一个线程释放它。

  1. SynchronousQueue 会阻塞,直到一个线程准备好获取数据为止,另一个线程将尝试放入数据。
  2. SynchronousQueue 没有卷。也就是说,它不包含数据。
  3. SynchronousQueue 用于实现前向排队策略,其中线程将控制权传递给等待线程,或者在允许的情况下创建一个新线程,否则不转移控制权。
  4. 该队列不允许空数据。尝试添加 null 元素将抛出NullPointerException
  5. 如果您使用 Collection 中的其他方法(例如 contains),SynchronousQueue 的行为就像一个空集合。
  6. 您不能使用 SynchronousQueue 的 peek 方法,因为该元素仅在您尝试删除它时才存在;此外,在另一个线程尝试删除元素之前,您将无法插入元素(使用任何方法)。
  7. 您将无法对 SynchronousQueue 使用迭代器,因为...... 它没有元素。
  8. SynchronousQueue 可以使用公平规则创建,其中按 FIFO 顺序保证对线程的访问。
也许这就是Java中SynchronousQueue的全部内容我们研究了这个多线程集合的一些特殊功能,并学习了如何使用 Java 中的 SynchronousQueue 解决经典的生产者-消费者问题。顺便说一句,称其为队列并不完全正确,因为...... 它不包含元素。在另一个线程调用 take() 之前,对 put() 的调用不会完成。更正确的做法是将其视为线程的聚会场所,线程在此共享一个对象。换句话说,它是 Java 中对象同步传递的实用程序,也许是wait 和 notification方法的更安全的替代方案。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION