This week, we are going to talk about a problem we may not notice in common concurrency code but may bite you later.
Synchronize on Method Call
Quick Question
See the following code: thread one will start first, then thread two. So, will the first element of list printed? Or deeper, can second thread enter that synchronized block?
List list;
// thread one
synchronized (list) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// thread two
synchronized (list.get(0)) {
System.out.println(list.get(0));
}
Answer
The answer is yes. Do you get it? let’s analyse what the essence of this problem.
First, we can simply find the the lock of list is always hold by thread one for its infinite loop. So, whether the second thread can enter that synchronized block depends on whether we need to get the lock of list.
Now, we need to understand: whether we get option one or option two when synchronize on a method call?
synchronized (list.get(0)) {}
// option one
obj = list.get(0);
synchronized (obj) {}
// option two
synchronized (list) {
synchronized (list.get(0)) {}
}
Either by trying with code, or viewing the byte code like following:
ICONST_0
INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
...
MONITORENTER <--- synchronization starts here
we can know compiler give us option one, i.e. we first get element, then only acquire one element lock.
Motivation
When it comes to the motivation, the following is my guess according to my experience.
In the perspective of compiler, nested synchronized block can be prone to deadlock if the acquisition of locks in disorder and compiler should not do it by itself.
In the perspective of programmer, if compiler acquire lock of list first, then the lock of element after compilation in which the result is invisible to programmer, it will be very dangerous, either in readability or in correctness aspects.
More
Now, what about the following code? Will the first element of array printed?
// thread one
synchronized (args) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleeping1");
}
}
// thread two
synchronized (args[0]) {
System.out.println(args[0]);
}
Short Answer: Yes
You can now run code or view byte code to verify it.
Ref
Written with StackEdit.
评论
发表评论