Run the given BoundedBuffer
class a couple of times. What range of undesirable behavior do you observe on your computer? (e.g. exceptions, invariant violations, hangs, etc.)
Your objective is to make the BoundedBuffer
class thread-safe by applying the monitor pattern.
The first step is to identify which fields of the class are mutable and not themselves thread-safe; these fields will need to be guarded by one or more locks (mutexes). This is part of the implementer’s spec and must be documented! We can achieve this succinctly using a “guarded by” clause in each field’s JavaDoc comment: the clause “guarded by: foo” means that a mutex must be acquired on object foo
before the field may be accessed. For monitors, the appropriate lock object is this
(remember: every Java Object has its own mutex and condition variable).
Add this clause for all fields where it is necessary. Are there any fields that do not need to be guarded?
Next, any code in the class’s methods that accesses guarded fields must synchronize on the appropriate locks. Remember that when the synchronized
keyword is used without an argument, it synchronizes on this
by default, appropriate for the monitor pattern. Additionally, if nearly all of a method’s body must be run while holding the lock, then the synchronized
keyword can be moved to the method declaration for convenience.
Add synchronized
to methods where it is needed. While doing so, consider the following questions:
The thread-safe version of the class should block when the desired operation cannot currently be performed. (Note that blocking methods in Java are almost always marked by throws InterruptedException
; this has already been done for you.) Blocking (and un-blocking) threads involves the use of condition variables, and for monitors, the object’s intrinsic condition variable (associated with its intrinsic mutex) should be used.
Replace the precondition checks with code that waits until the condition is satisfied. Don’t forget to notify other threads if conditions they were waiting for might have changed as a result of the operation. Once that’s done, remove the futile attempt at client-side blocking from the producer and consumer tasks.
Run the main()
method with your changes. Has the undesired behavior gone away? Note that concurrency bugs can be subtle, rare, and system-dependent, so testing alone is insufficient evidence of their absence. Following reliable patterns and performing careful analysis are important when trying to write safe concurrent code.
notify()
vs. notifyAll()
Since each operation on our buffer only adds or removes a single element at a time, it seems wasteful to wake up all waiting threads when only one of them could now make progress. Would using notify()
instead of notifyAll()
be a safe optimization? If not, what would the failure mode look like?
Ensure that your group is formed and your work submitted before the Friday evening deadline.