Programming Assignment 2
CS 51590
Parallel Programming
Spring, 2025

This assignment makes use of the files contained in this zip file. This assignment is due Wednesday, March 26.

This assignment is based on the producer-consumer.zip and bounded-bufer.zip examples that we used in class.

This assignment has three parts.

In the zip file there is the file Buffer.java which implements a simulation of a last-in-first-out (stack) buffer. This simulated buffer doesn't really hold any data, it just keeps track of how many entries there are in the buffer and it prints a representation of the buffer to stdout every time an item is "put in" or "removed from" the buffer. This buffer is not thread safe; it is not synchronized in any way. If multiple threads access this buffer concurrently, then this buffer will become corrupted.

In the zip file there is a file Buffer_Bounded.java which subclasses Buffer.java and uses a lock and two condition variables to implement a bounded buffer. This is the example that we created in class in bounded-bufer.zip.

In the zip file there are files ProducersConsumers_v1.java, Producer_v1.java, and Consumer_v1.java that are a producer-consumer simulation with three bounded-buffers, two producer threads, and one consumer thread. Each producer thread is in an endless loop where it randomly chooses one of the three buffers, puts an "item" into the buffer, and then displays the contents of the buffer in the console window. The consumer thread is in an endless loop where it randomly chooses one of the three buffers, takes an "item" from the buffer, and then displays the contents of the buffer in the console window. This simulation is prone to deadlock. The first part of this assignment is for you to write an explanation file, Deadlock_explanation.txt, of how and why this simulation can end up deadlocked. Also answer these questions: If there is only a single producer thread and a single consumer thread but multiple bounded-buffers, can there be a deadlock? If there are ten producer threads and ten consumer threads but only two bounded-buffers, can there be a deadlock?

The next two parts of this assignment have you implement two well know strategies for solving this deadlock problem. The first strategy uses non-blocking versions of the put() and get() methods. The second strategy uses timed versions of the put() and get() methods.

For the second part of this assignment, in the zip file there are files ProducersConsumers_v2.java, Producer_v2.java, Consumer_v2.java and Buffer_NoBlock.java that you are to complete so that they implement a producer-consumer simulation with three bounded-buffers, two producer threads, one consumer thread, and no deadlocks. You will subclass Buffer_Bounded.java with the class Buffer_NoBlock.java that has non-blocking putNB() and getNB() methods.

The non-blocking putNB() method should acquire the buffer's lock, then check if the buffer is full. If so, the method should immediately release the buffer's lock and return false to denote that the put operation failed. If the buffer is not full, then the method should call the inherited super class put() method. When the super class put() returns, the non-blocking putNB() method should release the buffer's lock and return true to denote successfully putting an item into the buffer.

The non-blocking getNB() method should acquire the buffer's lock, then check if the buffer is empty. If so, the method should immediately release the buffer's lock and return an empty Optional object to denote that the get operation failed. If the buffer is not empty, then the method should call the inherited super class get() method. When the super class get() returns, the non-blocking getNB() method should release the buffer's lock and return a non-empty Optional object containing the character successfully retrieved from the buffer by the super class get() method.

For the third part of this assignment, in the zip file there are files ProducersConsumers_v3.java, Producer_v3.java, Consumer_v3.java, and Buffer_TimeOut.java that you are to complete so that they implement another producer-consumer simulation with three bounded-buffers, two producer threads, one consumer thread, and no deadlocks. This time, you will subclass Buffer_Bounded.java with the class Buffer_TimeOut.java that has timed versions of the put() and get() methods.

Timed methods have a parameter that specifies the maximum amount of time that the method should remain blocked. When the timed put() method reaches its maximum time limit, the method should return false to denote that the put operation failed. If the timed put() method succeeds within its time limit, then it should return true to denote successfully putting an item into the buffer.

When the timed get() method reaches its maximum time limit, the method should return an empty Optional object to denote that the get operation failed. If the timed get() method succeeds within its time limit, then it should return a non-empty Optional object containing the character successfully retrieved from the buffer.

You implement the timed versions of the put() and get() methods by using the timed version of the await() method. The code for the timed versions of put() and get() will look a lot like the code for the untimed versions in the Buffer_Bounded class. The timed versions should use the timed await() method and appropriately handle the case of a timed-out call to await().

Each Producer_v3 and Consumer_v3 that calls a timed put() or get() determines the amount of time to wait before a timeout occurs. Use a 200 millisecond wait time.

It is worth noting that the non-blocking putNB() and getNB() methods and the timed put() and get() methods do not need to be in subclasses of the Buffer_Bounded class. They really should be methods in the Buffer_Bounded class, giving users of that class a choice between blocking, non-blocking, and timed version of the get and put operations. This assignment uses separate sub-classes just to make the problems a bit easier to describe and understand.

Here is an interesting problem to think about. What if there are, say five non-blocking bounded-buffers, fifty producer threads, and five consumer threads. Because there would be so many more producer threads than consumer threads, the small number of (non-blocking) buffers will almost always be full. How will this affect the producer threads? Can you think of a way to mitigate the affect that this will have on the overall performance of the program? Does the timed version of bounded buffer have a similar problem?

Turn in a zip file called CS51590Hw2Surname.zip (where Surname is your last name) containing your versions of Deadlock_explanation.txt, Producer_v2.java, Consumer_v2.java, Producer_v3.java, Consumer_v3.java, Buffer_NoBlock.java, and Buffer_TimeOut.java.

This assignment is due Wednesday, March 26.