SynchronousQueue Example in Java – Producer Consumer Solution
SynchronousQueue is special kind of BlockingQueue in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa. When you call put() method on SynchronousQueue it blocks until another thread is there to take that element out of the Queue. Similarly, if a thread tries to remove an element and no element is currently present, that thread is blocked until another thread puts an element into the queue. You can correlated SynchronousQueue with athletes (threads) running with Olympic torch, they run with torch (object need to be passed) and passes it to other athlete waiting at other end. If you pay attention to the name, you will also understand that it is named SynchronousQueue with a reason, it passes data synchronously to other thread; it wait for the other party to take the data instead of just putting data and returning (asynchronous operation). If you are familiar with CSP and Ada, then you know that synchronous queues are similar to rendezvous channels. They are well suited for hand-off designs, in which an object running in one thread must sync up with an object running in another thread in order to hand it some information, event, or task. In earlier multi-threading tutorials we have learned how to solve producer consumer problem using wait and notify, and BlockingQueue and in this tutorial we will learn how to implement producer consumer design pattern using synchronous queue. This class also supports an optional fairness policy for ordering waiting producer and consumer threads. By default, this ordering is not guaranteed. However, a queue constructed with fairness property set to true grants threads access in FIFO order.
Producer Consumer Solution using SynchronousQueue in Java
As I have said before, nothing is better than a producer consumer problem to understand inter-thread communication in any programming language. In Producer consumer problem, one thread act as producer which produces event or task and other thread act as consumer. Shared buffer is used to transfer data from producer to consumer. Difficulty in solving producer consumer problem comes with edge cases e.g. producer must wait if buffer is full or consumer thread must wait if buffer is empty. Later one was quite easy as blocking queue provides not only buffer to store data but also flow control to block thread calling put() method (PRODUCER) if buffer is full, and blocking thread calling take() method (CONSUMER) if buffer is empty. In this tutorial, we will solve the same problem using SynchronousQueue, a special kind of concurrent collection which has zero capacity.
In following example, we have two threads which is named PRODUCER and CONSUMER (you should always name your threads, this is one of the best practice of writing concurrent application). First thread, publishing cricket score, and second thread is consuming it. Cricket scores are nothing but a String object here. If you run the program as it is you won’t notice any thing different. In order to understand how SynchronousQueue works, and how it solves producer consumer problem, you either need to debug this program in Eclipse or just start producer thread by commenting consumer.start(); If consumer thread is not running then producer will block at
queue. put(event); call, and you won’t see [PRODUCER] published event: FOUR. This happens because of special behaviour of
SynchronousQueue, which guarantees that the thread inserting data will block until there is a thread to remove that data or vice-versa. You can test the other part of code by commenting producer. start(); and only starting consumer thread.
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 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
If you have send the output carefully then you would have noticed that order of events are reversed. Seems [CONSUMER] thread is consuming data even before [PRODUCER] thread has produced it. This happens because by default SynchronousQueue doesn’t guarantee any order, but it has a fairness policy, which if set to true allows access to threads in FIFO order. You can enable this fairness policy by passing true to overloaded constructor of SynchronousQueue i.e. new SynchronousQueue(boolean fair).
Things to remember about SynchronousQueue in Java
Here are some of the important properties of this special blocking queue in Java. It’s very useful to transfer data from one thread to another thread synchronously. It doesn’t have any capacity and blocks until there is a thread on the other end.
- SynchronousQueue blocks until another thread is ready to take the element, one thread is trying to put.
- SynchronousQueue has zero capacity.
- SynchronousQueue is used to implement queuing strategy of direct hand-off, where thread hands-off to waiting thread, else creates new one if allowed, else task rejected.
- This queue does not permit null elements, adding null elements will result in NullPointerException.
- For purposes of other Collection methods (for example contains), a SynchronousQueue acts as an empty collection.
- You cannot peek at a synchronous queue because an element is only present when you try to remove it; Similarly you cannot insert an element (using any method) unless another thread is trying to remove it.
- You cannot iterate over SynchronousQueue as there is nothing to iterate.
- A SynchronousQueue constructed with fairness policy set to true grants threads access in FIFO order.
That’s all about SynchronousQueue in Java. We have seen some special property of this special concurrent collection, and learned how to solve classical producer consumer problem using SynchronousQueue in Java. By the way calling it a Queue is bit confusing because it doesn’t have any capacity to hold your element. Call to put() operation will not complete until there is a thread which is calling take() operation. It’s better be a rendezvous point between threads to share objects. In other words, its a utility to synchronously share data between two threads in Java, probably a safer alternative of wait and notify methods.
Reference: | SynchronousQueue Example in Java – Producer Consumer Solution from our JCG partner Javin Paul at the Javarevisited blog. |