From the documentation of Condition
:
Condition
factors out the Object
monitor methods (wait
, notify
and notifyAll
) into distinct objects to give the effect of having multiple wait-sets per object, by combining them with the use of arbitrary Lock
implementations. Where a Lock
replaces the use of synchronized
methods and statements, a Condition
replaces the use of the Object
monitor methods.
Conditions (also known as condition queues or condition variables) provide a means for one thread to suspend execution (to "wait") until notified by another thread that some state condition may now be true. Because access to this shared state information occurs in different threads, it must be protected, so a lock of some form is associated with the condition. The key property that waiting for a condition provides is that it atomically releases the associated lock and suspends the current thread, just like Object.wait
.
A Condition
instance is intrinsically bound to a lock. To obtain a Condition
instance for a particular Lock
instance use its newCondition()
method.
As explained, a Condition
instance must be associated with a Lock
instance1. Having Lock
function as a factory for creating instances of Condition
makes perfect sense with that in mind as it implies the relationship between the two. Another way this relationship could have been enforced is to give Condition
a constructor which accepts a Lock
instance, but since Condition
is also an interface it cannot declare constructors. I'm also of the opinion that a no-argument factory method is more user friendly in this case anyway.
Note: If it's not already clear, the ReentrantLock
class is an implementation of the Lock
interface and the ConditionObject
class is an implementation of the Condition
interface.
The other problem with attempting to use ConditionObject
directly is that it's an inner class (i.e. non-static nested class) of AbstractQueuedSynchronizer
2. This means you would need an instance of the latter class in order to create an instance of the former class. However, the implementation of AbstractQueuedSynchronizer
used by ReentrantLock
is an implementation detail and not exposed to the public. In other words, you have no way to call the constructor of ConditionObject
which means the only way to create an instance is via newCondition()
.
To recap, there's at least three reasons why a factory method is used to create Condition
objects:
- It makes the relationship between
Lock
and Condition
clear.
- With both
Lock
and Condition
being interfaces, you need a way to associate a Condition
with a Lock
without knowing about the implementations. Otherwise it would not be possible to "program to an interface".
- Due to
ConditionObject
being an inner class it cannot be instantiated directly—at least, not by code which doesn't have access to an instance of the enclosing class.
1. The methods of Condition
only make sense in the context of owning a Lock
. Just like how a thread must be synchronized
on an object before it can legally invoke that object's monitor methods (i.e. wait/notify), a thread must own the associated Lock
before it can legally invoke the methods of the Condition
(i.e. await/signal).
2. There's also AbstractQueuedLongSynchronizer
which declares its own ConditionObject
inner class. While the class has the same name as the one declared by AbstractQueuedSynchronizer
, the two are actually separate classes.