Discussion 13: Synchronized Buffers
In today’s discussion, you’ll get some practice with synchronization in Java and reasoning about multithreaded programs. You’ll be working with a concurrent ring buffer, a fixed-size queue where producers add items and consumers remove them. When multiple producer and consumer threads operate on the same resource, synchronization is required to prevent race conditions.
Learning Outcomes
- Identify race conditions in a given piece of concurrent code and list the possible states the program can be in after it executes.
- Use Java's
Threadclass to write concurrent code. - Explain the semantics of locks in Java and write code involving synchronization.
- Explain how condition variables can be used to synchronize modifications of a shared resource.
Reminder: Discussion Guidelines
The work that you complete in discussion serves as a formative assessment tool; it offers the opportunity to assess your understanding of the material and for our course staff to get a “pulse” on how things are going, so we can make adjustments in future classes. Your grade in discussion is based on your attendance, active participation, and completion of that day’s activity. More information about the grading and expectations can be found in the syllabus.
Since discussion activities are not graded for correctness, we do not restrict what resources you may use to complete them, which include notes, books, unrestricted conversations with other students, internet searches, and the use of large language models or other generative AI. We advise you to be pragmatic about your use of these resources and think critically about whether they are enhancing your learning. Discussion activities are intended to serve as “strength training” for programming tasks we will expect on assignments and exams (and that you will encounter in future courses and careers), and their main benefit comes from thinking critically to “puzzle” them out.
Unsynchronized Ring Buffer
We’ll be working with RingBuffer.java first; this contains a non-synchronized, non-blocking implementation of a ring buffer backed by an array. Take a minute to inspect the code, in particular the enqueue() and dequeue() methods.
This implementation will behave as intended if it is run by a single-threaded program, but may exhibit incorrect behavior if there are multiple producer and consumer threads. To see this, you’ll work through an example and identify the possible race conditions.
RingBuffer object buffer containing Integers, and the current state of the array backing store for our buffer looks like this:
You can also assume that, at this point,
iHead == 0 and iTail == 3.
Then, suppose the following enqueue calls are executed on separate threads:
|
|
|
|
buffer (specifically, the backing array and iTail) after these calls are executed.
Synchronized Ring Buffer
Now look at the ConcurrentRingBuffer.java file in the release code. Once completed, this class will model a ring buffer that can be safely accessed concurrently by multiple threads.
While most of this class has been written (and is very similar to the RingBuffer from before), it is missing thread-safe definitions of enqueue() and dequeue() (TODOs 1-2). These implementations should block if there is nothing to be done; that is, if there is no space in the queue to add a new element, or if there is no element in the queue to be consumed.
synchronized:
wait():
notifyAll():
Complete the definitions of the
enqueue() and dequeue() methods according to their specifications. Use the wait() and notifyAll() methods to achieve proper synchronization.Run the program and observe its output. The output may vary across runs, since it is dependent on what order the threads are scheduled in for execution (which we do not control here). However, you should see that elements are always dequeued in the same order in which they are enqueued. It will also be useful to look at the
main() method and understand what it's doing; if you'd like, you can also play around with the size of the buffer and the number of producer and consumer threads, and observe how that affects the program's behavior.
Submission
At the end of class, your group’s primary author should create a PDF with your written answers and upload this to the “Discussion 13” Gradescope assignment, tagging all other members of the group onto the submission.