Reputation: 821
If Condition
could be used separately itself and creating code is merely:
final ConditionObject newCondition() {
return new ConditionObject();
}
why it's not created just this way?
In class ArrayBlockingQueue
there is code in constructor:
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
where both notEmpty
and notFull
are instances of Condition
class.
Upvotes: 0
Views: 453
Reputation: 2427
As mentioned by Slaw, instantiating a new Condition separately just doesn't make sense as it will lost association with the original Lock object, and it would just similar to creating a new Condition from another Lock (because java.util.concurrent.locks.Condition cannot be instantiated separately).
We can refer to below 2 examples to understand more:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
final ReentrantLock lock = new ReentrantLock();
final Condition notEmpty = lock.newCondition();
final Condition notFull = lock.newCondition();
String message;
boolean ready;
boolean isCompleted;
public void consume() {
ReentrantLock lock = this.lock;
lock.lock();
try {
while(!ready)
notEmpty.await();
System.out.println("Received message: " + message);
ready = !ready; // reverse the "ready" state
notFull.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void publish(String message) {
ReentrantLock lock = this.lock;
lock.lock();
try {
while(ready)
notFull.await();
System.out.println("Adding message");
this.message = message;
ready = !ready; // reverse the "ready" state
notEmpty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionTest ct = new ConditionTest();
Thread msgProducerThread = new Thread(() -> {
List<String> messages = new ArrayList<String>();
messages.add("Hello!");
messages.add("here we get a message");
messages.add("then we get another message");
messages.forEach(m -> ct.publish(m));
ct.isCompleted = true;
});
Thread msgConsumerThread = new Thread(() -> {
while (!ct.isCompleted)
ct.consume();
});
msgProducerThread.start();
msgConsumerThread.start();
}
}
Then we would get below result which means the Lock and Condition are functioning properly:
Adding message
Received message: hello
Adding message
Received message: current project is complete
Adding message
Received message: here is the estimation for new project
However, if we try to use separate Condition by replacing the original code:
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
with :
ReentrantLock lock2 = new ReentrantLock(fair);
ReentrantLock lock3 = new ReentrantLock(fair);
Condition notEmpty = lock2.newCondition();
Condition notFull = lock3.newCondition();
Then, it would get:
Adding message
Exception in thread "Thread-0" Received message: Hello!
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at ConditionTest.publish(ConditionTest.java:54)
at ConditionTest.lambda$0(ConditionTest.java:75)
at java.lang.Thread.run(Thread.java:745)
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at ConditionTest.consume(ConditionTest.java:33)
at ConditionTest.lambda$1(ConditionTest.java:83)
at java.lang.Thread.run(Thread.java:745)
Which means there are no relevant association between the Condition and the Lock, which leads to IllegalMonitorStateException being thrown.
Upvotes: 1
Reputation: 46116
From the documentation of Condition
:
Condition
factors out theObject
monitor methods (wait
,notify
andnotifyAll
) into distinct objects to give the effect of having multiple wait-sets per object, by combining them with the use of arbitraryLock
implementations. Where aLock
replaces the use ofsynchronized
methods and statements, aCondition
replaces the use of theObject
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 aCondition
instance for a particularLock
instance use itsnewCondition()
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:
Lock
and Condition
clear.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".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.
Upvotes: 1
Reputation: 73568
Locks and Conditions provide a mechanism that was previously (pre 1.5) available only through synchronization
, wait/notify
and custom condition code.
Here's an idiomatic example
synchronized(foo) { // Lock
while(!conditionMet) // Condition
foo.wait(); // Condition.signalAll();
// Condition met, do something and notify
conditionMet = false;
foo.notifyAll();
}
It might seem that the condition isn't related to the lock object in any way, but that guarantees thread safety. You can't write the above code without access to the condition being inside the synchronized block. (You can access the condition boolean from non-threadsafe code and mess things up, but you can always mess things up).
The whole ReentrantLock
class is actually a functionality wrapper over the Sync
class object, which is responsible for the low level synchronization and the creation of conditions. Conditions are bound to a specific Lock
(or Sync
rather) to basically make sure that they're thread-safe. As the Javadoc for Condition
states
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.
Upvotes: 1