SynchronousQueue是BlockingQueue的一种特殊类型,其中每个插入操作必须等待另一个线程进行相应的删除操作,反之亦然。 当您在SynchronousQueue上调用put()方法时,它将阻塞,直到有另一个线程将该元素从Queue中取出为止。 同样,如果一个线程尝试删除一个元素并且当前不存在任何元素,则该线程将被阻塞,直到另一个线程将一个元素放入队列中为止。 您可以将SynchronousQueue与运行奥运火炬的运动员( 线程 )相关联,使他们运行火炬(需要传递对象)并将其传递给在另一端等待的其他运动员。 如果您注意该名称,您还将了解到它被命名为SynchronousQueue是有原因的,它将数据同步传递到其他线程。 它等待对方获取数据,而不仅仅是放入数据并返回(异步操作)。 如果您熟悉CSP和Ada,那么您就会知道同步队列类似于集合通道。 它们非常适合切换设计,在该设计中,在一个线程中运行的对象必须与在另一个线程中运行的对象同步,以便向其传递一些信息,事件或任务。 在早期的多线程教程中,我们学习了如何使用wait and notify和BlockingQueue解决生产者消费者问题,在本教程中,我们将学习如何使用同步队列来实现生产者消费者设计模式 。 此类还支持用于订购等待的生产者和使用者线程的可选公平性策略。 默认情况下,不保证此排序。 但是,将Fairness属性设置为true构造的队列将按FIFO顺序授予线程访问权限。
正如我之前说过的,没有什么比生产者使用者问题更好地理解任何编程语言中的线程间通信了。 在生产者使用者问题中,一个线程充当产生事件或任务的生产者,而另一个线程充当使用者。 共享缓冲区用于将数据从生产者传输到消费者。 解决生产者使用者问题的困难在于边缘情况,例如,如果缓冲区已满,生产者必须等待,如果缓冲区为空,使用者线程必须等待。 后来一个很容易,因为阻塞队列不仅提供缓冲区来存储数据,而且还提供流控制来阻塞线程,如果缓冲区已满,则调用put()方法(PRODUCER),如果阻塞为空,则阻塞线程将调用take()方法(CONSUMER) 。 在本教程中,我们将使用SynchronousQueue(一种零容量的特殊并发集合)解决相同的问题。
在下面的示例中,我们有两个线程,分别名为PRODUCER和CONSUMER(您应始终命名线程,这是编写并发应用程序的最佳实践之一)。 第一线程,发布板球得分,第二线程正在消耗它。 板球比分不过是一个String对象。 如果您按原样运行程序,则不会发现任何不同。 为了了解SynchronousQueue的工作原理以及如何解决生产者使用者问题 ,您需要在Eclipse中调试该程序,或者只是通过注释consumer.start()来启动生产者线程。 如果使用者线程未运行,则生产者将在以下位置阻塞
队列。 put(event); 通话,您将不会看到[PRODUCER]发布的活动:四个。 发生这种情况是由于 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<String> queue = new SynchronousQueue<String>();Thread producer = new Thread("PRODUCER") {public void run() {String event = "FOUR";try {queue.put(event); // thread will block hereSystem.out.printf("[%s] published event : %s %n", Thread.currentThread().getName(), event);} catch (InterruptedException e) {e.printStackTrace();}}};producer.start(); // starting publisher threadThread consumer = new Thread("CONSUMER") {public void run() {try {String event = queue.take(); // thread will block hereSystem.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
如果您仔细发送了输出,那么您会注意到事件的顺序是相反的。 似乎[CONSUMER]线程正在消耗数据,甚至在[PRODUCER]线程产生数据之前。 发生这种情况是因为默认情况下,SynchronousQueue不保证任何顺序,但是它具有公平性策略,如果将其设置为true,则可以按FIFO顺序访问线程。 您可以通过将true传递给SynchronousQueue的重载构造函数 (即新的SynchronousQueue(boolean fair))来启用此公平性策略。
这是Java中此特殊阻塞队列的一些重要属性。 将数据从一个线程同步传输到另一个线程非常有用。 它没有任何容量,只有在另一端有线程时才阻塞。
- SynchronousQueue阻塞,直到另一个线程准备好接受该元素,一个线程正在尝试放置该元素。
- SynchronousQueue的容量为零。
- SynchronousQueue用于实现直接切换的排队策略,在该策略中,线程将切换到等待的线程,否则允许创建新线程,否则拒绝任务。
- 此队列不允许使用null元素,添加null元素将导致NullPointerException 。
- 出于其他Collection方法(例如contains)的目的,SynchronousQueue充当空集合。
- 您无法窥视同步队列,因为仅当您尝试删除它时,该元素才存在。 同样,您不能插入元素(使用任何方法),除非另一个线程试图将其删除。
- 您无法在SynchronousQueue上进行迭代,因为没有要进行迭代的内容。
- 将公平性策略设置为true构造的SynchronousQueue授予线程按FIFO顺序的访问权限。
这就是Java中的SynchronousQueue 。 我们已经看到了此特殊并发集合的某些特殊属性,并学习了如何使用Java中的SynchronousQueue解决经典的生产者使用者问题。 顺便说一下,队列有点令人困惑,因为它没有任何能力容纳您的元素。 在有一个线程正在调用take()操作之前,对put()操作的调用不会完成。 最好是线程之间共享对象的集合点。 换句话说,它是一个实用程序,可以在Java中的两个线程之间同步共享数据,这可能是更安全的wait和notify方法的替代方法。
翻译自: https://www.javacodegeeks.com/2014/06/synchronousqueue-example-in-java-producer-consumer-solution.html