Friday, September 15, 2023

How to Create a Thread-Safe Bounded Buffer in Java? Example

This is one of the common coding problems for Java developers which the interviewer asks to check both their coding skills as well to test their concurrency skills. A BoundedBuffer is a common data structure used in concurrent Java applications to pass data between threads. For example, you can use Bounded Buffer to implement the Producer-Consumer pattern in Java. Producer thread can put items for consumers to process and can wait if Buffer is full. This pattern allows Consumers to process items already placed in queue or buffer.  

In this article, I will show you how to implement Bounded Buffer in Java. We will also use Generic to create a TypeSafe Bounded Buffer. A typesafe bounded buffer can hold any type of object and you can get it without typecasting. 


BoundedBuffer Implementation in Java - Example

There are a couple of important things to note while implementing the Bounded Buffer data structure in Java. Like, you need a way to stop threads when the buffer is full. We also need to make the class thread-safe because multiple threads will work with our bounded buffer at the same time. For holding objects, we will just use Array. 

How to Implement Bounded Buffer in Java? Example



1. Java Program to implement Bounded Buffer in Java 

Here is our complete Java program to implement a thread-safe, Bounded Buffer in Java. I have not used any third-party library and just used core Java classes 
/**
 * Java program to implement BoundedBuffer using wait and notify.
 *
 * @author WINDOWS 8
 */

public class Test {

    public static void main(String args[]) {

        BoundedBuffer<String> buffer = new BoundedBuffer<String>(2);

        // bounded buffer must be empty when first created
        System.out.println("Is buffer empty? " + buffer.isEmpty());

        buffer.put("abc");
        buffer.put("def");

        // buffer should be full after putting two elements
        System.out.println("Is buffer full? " + buffer.isFull());

        String value = buffer.take();
        System.out.println("element taken from buffer : " 
                                + value);
        value = buffer.take();
        System.out.println("another element taken from buffer : " 
                                + value);

        // buffer should be empty, after taking out all elements
        System.out.println("Is buffer empty again? " + buffer.isEmpty());
       
        // now an attempt to take another element should block
        buffer.take();
        System.out.println("This line will not print because main thread is blocked");

    }

}

class BoundedBuffer<T> {

    private final Object[] buffer;
    private int putpos, takepos, count;

    public BoundedBuffer(int bound) {
        buffer = new Object[bound];
    }

    /*
     * This method blocks until buffer becomes empty
     */
    public synchronized void put(T object) {
        try {
            while (isFull()) {
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        doPut(object);
        notifyAll();
    }

    /*
     * This method blocks until the buffer becomes full
     */
    public synchronized T take() {
        try {
            while (isEmpty()) {
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        T element = doTake();
        notifyAll();
        return element;
    }

    public synchronized boolean isFull() {
        return count == buffer.length;
    }

    public synchronized boolean isEmpty() {
        return count == 0;
    }

    protected synchronized void doPut(T object) {
        buffer[putpos] = object;
        if (++putpos == buffer.length) {
            putpos = 0;
        }
        ++count;
    }

    protected synchronized T doTake() {
        T element = (T) buffer[takepos];
        if (++takepos == buffer.length) {
            takepos = 0;
        }
        --count;
        return element;
    }
}

Output
Is buffer empty? true
Is buffer full? true
element taken from buffer : abc
another element taken from buffer : def
Is buffer empty again? true
You can see that we first check if our Bounded Buffer is empty and then we put two items and then check again if the buffer is full because the size of our bounded buffer is two, which means it can only hold 2 items.

On the design side, I  have not created any interface but if you want, you can also create an interface called Buffer which take(), put(), isEmpty(), isFull(), and size methods, and then implement the interface with a class called BoundedBuffer. 


That's all about how to implement Bounded Buffer in Java. You can use this buffer to solve many classic concurrency problems like a producer-consumer problem. As a task for you, I suggest writing some JUnit tests for this class and check if they work properly in concurrent Java applications. 

How will you create tests to check concurrency? Well, just use the Executor pattern to create multiple threads which can put the object into the buffer and multiple consumer threads which can retrieve the values from the buffer. 

And, what is your favorite Java programming exercise? Producer Consumer problem, Palindrom, Prime Number, Binary Search, or this one?  

Do let me know in comments. 

No comments:

Post a Comment

Feel free to comment, ask questions if you have any doubt.