I have three questions to the code below: 1. Why are okToPut and okToGet here two objects but not simply two boolean variables? What are their functions? 2. I don�t quite understand the four synchronized in line 13, 21, 27 and 35. What are they for? 3. Does this program demostrate anything about monitor? There are one producer and one consumer, only one place in the buffer, the proper sequence should be put-get-put-get. Is there anyone could kindly tell me, thank you very much in advance.
[ December 27, 2002: Message edited by: Ellen Fu ]
This can do a much better job at explaining things than I possibly can, Ellen. Sun's Java Tutorial part on Threads Look in particular at the part on synchronizing threads. -Barry [ December 29, 2002: Message edited by: Barry Gaunt ]
Barry, Thank you very much. I have got some idea about my first question. Because only objects can do things like wait(), notify(), etc, booleans cannot. But still not very clear about the latter two. Thank you for the resource you liked here. I�ll try to pick out the useful information from there.
Regards, Ellen [ December 30, 2002: Message edited by: Ellen Fu ]
Originally posted by Ellen Fu: 3. Does this program demostrate anything about monitor?
Yes, it demonstrates that developers better know what they're doing before trying fancy stuff Where did you find this code? It is buggy and would cause some magnificent deadlocks if you'd ever try to run it. This is a typical single-slot buffer with an arbitrary number of interchangeable putter and getter threads. The idea behind separate "okToPut" and "okToGet" monitor objects is that, when you put an object in the buffer, you can wake up exactly one getter thread by calling okToGet.notify() (there's a typo in the code there, it should read synchronized(okToGet) -- you can only call notify() or wait() if you actually have a lock on the object). Conversely, when a getter has emptied the buffer, it can notify exactly one putter by calling okToPut.notify(). The alternative would be to have just a single monitor, probably the Buffer instance itself, and to call notifyAll() after a successful put or get. This means that all waiting threads are woken up, and they'll have to find out by examining the count variable whether there's anything useful for them to do. This is simpler, but can be less efficient if there are lots and lots of threads waiting. I'll illustrate that below. So where does the deadlock kick in? Well, the author of the code obviously realised that the testing and manipulation of the count variable needs to be synchronized as well, and simply made the get() and put() methods synchronized. Bad move. Imagine for a moment that the buffer is empty (count == 1) and we're calling get(). You end up inside the while loop -- which should have been a simple "if" -- and okToGet.wait() is called. This call releases the monitor lock on the okToGet object, but not the one on the buffer itself. As a consequence, all calls to put() will block, and the thread will never be woken up. Deadlock. In general, potential for deadlock exists whenever you acquire locks on more than one object at a time. I'll stick my neck out and try to think up a possible fix without testing the code Neither put() nor get() ever acquires a lock on Buffer itself. The deadlock potential is now restricted to the second half of each method, where you acquire locks on both okToPut and okToGet. If both the second halves were executed at the same time, deadlock would occur. But the if() statements at the start of each method guarantee that this cannot happen, so the code is threadsafe. A much simpler implementation doesn't use explicit monitor locks at all.This does not only save a few lines of code, it is significantly less complex. Note two major differences. First, because there is just one monitor, you cannot wake up a getter or a putter thread specifically and have to simply call notifyAll() to wake up all threads. Second, because a thread may now be woken up even though the condition it waits for isn't met, the wait() call is enclosed in a while loop instead of a simple if statement. Isn't the second much less efficient? Depends. When there are a lot of waiting threads, waking them all up takes time. On the other hand, a call to get() or put() performs only a single lock where the first implementation needs two. So in low-contention or high-throughput situations the second method is actually likely to be not only simpler, but faster as well. The lesson is to always use the simplest type of implementation and only contemplate something more complicated when profiling shows that the buffer actually is a bottleneck in your application. HTH - Peter
Joined: Sep 17, 2002
Peter, Thank you very much for the detailed, informative reply. You are soooooo kind. I am currently taking a course centered on concurrent programming. The code I pasted above is from our course note. In the note, moniter is introduced as an ADT and some abstract description of the operation WAIT and SIGNAL are given. The code is following the mathematical description. I have sent the link of this thread to my professor. Maybe he will come and have a look some time later. The recommended literature of our course is Concurrency: State Models & Java Programs. Much of the content of our course note is from this book. You may check the Chapter 5: Monitors & Condition Synchronization. But I didn�t find the code which I pasted here in that book. Your code enlighten me a lot. I appreciate you very much.
Peter den Haan
Joined: Apr 20, 2000
My pleasure. I realise now why the original code used while loops instead of a simple if -- to handle interrupts. When the wait() is interrupted and the condition isn't met, the code loops back to the wait(). Arguably, it should've followed the wait() by a break statement to make the logic explicit and to save a needless test. My revised code doesn't really handle interrupts. Cleanest would be to either use a while loop (if you want to ignore interrupts)Or to throw a RuntimeException in the catch clause (if you don't want to allow interrupts), or to allow the InterruptedException to propagate out of the method (if you want the client code to handle interrupts). Doug Lea's long-standing util.concurrent package contains first rate implementations of all the concurrent programming abstractions you know and love, and a couple you may never have heard about Not appropriate when you are trying to fathom the nitty-gritty of concurrent programming based on Java's native monitor lock concept, but very useful when you're trying to get work done and need to have some robust abstractions in your toolkit. There is a Java Specification Request to incorporate this in the Java platform. That'd be good; this synchronization business is rather low level in many cases. Mit freundlichem Gruss, - Peter [ December 30, 2002: Message edited by: Peter den Haan ]
Joined: Sep 17, 2002
Hi Dr. den Haan, I was trying to solve the problem in this thread, got frustrated and dug back here. The Doug Lea�s package is just...gorgeous I should had checked closely earlier. Thank you so much!
Best Regards, Ellen [ January 16, 2003: Message edited by: Ellen Fu ]