I am am rather embarrassed to admit that I was recently bitten by the synchonized keyword in Java... again... I have this motto that if you think you understand multi-threading, you don't. It can be by far one of the most complex and tricky things to do right... and is by far the most difficult to debug (in my humblest of opinions of course). It is also one of those kind of problems that can be hardware-specific (typically a faster computer, makes it break quicker). When a method is defined as synchronised, i.e.
...then it means that the contents of that method is defined as a critical section, which implies that only one thread can be inside that method at any one point in time. The synchronize modifier requires an object to lock on of course. For static methods (i.e. methods associated with the class), the lock is on the class object, i.e. Someclass.class, while objects lock on the instance of that class (i.e. the object). This of course has a pesky side-effect, that one must be aware of.
Consider the following code:
Since synchronize locks on the class itself, only one thread can be in any synchronized method at any one point in time. In this lies the confusion: It is natural to think that two different threads can be in two different synchronized methods at the same time. However, this is not the case. The output of the above code is unexpectedly as follows (well, for people like me):
Fortunately, Java 1.5 introduced the concept of a lock. This allows a specific section of code to be locked (by a given lock) and therefore defined as a critical section in isolation of other critical sections within the same class or object. Therefore, given the following code:
Here, a thread can be in either method at any one point of time, giving the expected output:
From the above, it is clear that foo() and bar() are executed simultaneously. However, multiple foo() methods are not executed at the same time (and the same for bar()). This can be confirmed since the text "I am in foo" will always be separated with "done with foo" (likewise with bar()).
Locks also allows multiple methods to be tied together in all kinds of arrangements. For example, if we write a method called fooFriend() and use FOOLOCK to lock it, only one thread can be in the critical sections of foo() and fooFriend() at the same time. This makes a Java lock a rather powerful construct.
The try-finally finally block is rather important and ensures that the lock is releases no matter what. A situation may arise where an exception is thrown inside the critical section and the code will go into a deadlock as the lock is still owned by the original thread.
Indeed synchronized can be a rather embarrassing monster when it rears it's ugly head.
No comments:
Post a Comment