In your example the method is both locking on the instance of Foo
and on the object bar
. Other methods may only be locking on the instance of Foo
or on the object bar
.
So, yes, this makes sense depending on exactly what they are doing. Presumably bar
protects some smaller subset of data, and some methods will only need to lock on bar
to perform their actions in a thread-safe manner.
What synchronized
does
synchronized
(on a method, or in a statement) creates a mutual exclusion zone (critical section or, specifically for Java, a reentrant mutex). The "key" for a thread to enter the critical section is the object reference used in the synchronized
statement ‡. Only one thread can (recursively) "own" this "key" at one time across all blocks that use the same key; that is, only one thread can enter any block synchronized
on a given object reference at one time.
Such a critical section simply prevents the operations (variable read/write) that you do inside the block happening concurrently with any other operations in all other critical sections that lock on the same object reference. (It doesn't automatically protect all variables inside an object).
In Java, such a critical section also creates a happens-before contract.
By example
As a somewhat contrived example †:
public class Foo {
final Bar bar = new Bar();
private int instanceCounter = 0;
private int barCounter = 0;
public synchronized void incrementBarCounterIfAllowed() {
synchronized (bar) {
if (instanceCounter < 10) barCounter++;
}
}
public synchronized void incrementClassCounter() {
instanceCounter++;
}
public void incrementBarCounter() {
synchronized (bar) {
barCounter++;
}
}
}
Whether the instance variables are private or not doesn't really matter to whether this approach is applicable. In a single class you can have multiple lock objects, each of which protect their own set of data.
However the risk of doing this is that you have to be very strict with coding conventions to prevent deadlocks by locking two locks in different orders in different places. For example, with the above code if you then do this from somewhere else in the code:
synchronized(myFoo.bar) {
myFoo.incrementClassCounter();
}
you risk a deadlock with the incrementBarCounterIfAllowed()
method
† Note that barCounter
could be an instance variable for Bar
etc etc - I avoided that for the sake of brevity in the code sample.
‡ In the case of synchronized
methods, that reference is the reference to the class instance (or to the Class<?>
for the class for static
methods).