Reputation: 3
I need help in understanding the below code :
private Predicate composedPredicate = null;
public boolean evaluate(Task taskData) {
boolean isReadLock = false;
try{
rwl.readLock().lock();
isReadLock = true;
if (composedPredicate == null) {
rwl.readLock().unlock();
isReadLock = false;
rwl.writeLock().lock();
if (composedPredicate == null) {
//write to the "composedPredicate" object
}
}
}finally {
if (isReadLock) {
rwl.readLock().unlock();
}else{
rwl.writeLock().unlock();
}
}
return composedPredicate.test(taskData);
}
What will happen if we don't use Read Locks in the above code? Like :
public boolean evaluate(Task taskData) {
//boolean isReadLock = false;
try{
//rwl.readLock().lock();
//isReadLock = true;
if (composedPredicate == null) {
//rwl.readLock().unlock();
//isReadLock = false;
rwl.writeLock().lock();
if (composedPredicate == null) {
//write to the "composedPredicate" object
}
}
}finally {
rwl.writeLock().unlock();
}
return composedPredicate.test(taskData);
}
Upvotes: 0
Views: 1172
Reputation: 1912
This code is simular to an old 'Singleton-pattern' wich makes use of the synchronozed blocks. E.g.
class Singleton
{
volatile Singleton s;
public static Singleton instance()
{
if(s == null)
{
synchronized(Singleton.class)
{
if(s == null)
s = new Singleton();
}
}
return s;
}
}
Notice the double 'null-check' where only the second one is synchronozed. The reason for doing the first 'null-check' is to prevent the blocking of threads if the instance()
method is called (because when not null, it can proceed without synchronization).
Your first code is doing the same. First it checks if there is something assigned to composedPredicate
. And if that isnt the case, only than will it aquire a writingLock (wich blocks all other Thread oposed to readLocks, which only blocks writeLocks).
The main difference with the 'singleton-pattern' and your code is that your value can be reassignes. This can only happen safly if it makes sure nobody is reading the value during modification. By removing the readLock you basically create a possibility that a thread may get undefined results (if not a crash) when accessing the composedPredicate
while another Thread is modifying that same field.
So to answer your questions: 1. You dont need a readLock for writing, only a writeLock (wich will block all other Threads whonare trying to lock). But in this design-pattern you cannot leave it out. 2. & 3. See explanation above.
Hope this was enough to get a grasp of this pattern.
EDIT As commented by Erwin Bolwidt , the above pattern is considered broken (without the 'volatile' keyword) due to compiler/CPU code optimization (where read/write actions may happen out of order). In the linked blog there are examples for alternatives/fixes for this problem. It turns out the 'volatile' keyword creates a barier which disallows reordering of read and write operations by either the compiler or CPU optimization, and thus 'fixes' the 'double-checked-locking' pattern described above.
Upvotes: -1
Reputation: 31299
The first code that you posted is a correct implementation of the double-checked locking approach in Java using a read/write lock.
Your second implementation without a read-lock is broken. The memory model allows writes to be reordering from the perspective of another thread seeing the result of the writes to memory.
What could happen is that you could be using a not-fully initialized instance of Predicate in the thread that is reading it.
Example with your code:
We have thread A and B both running evaluate
and composedPredicate is null
initially.
composedPredicate
is null
Predicate
composedPredicate
composedPredicate
is not null
composedPredicate.test(taskData);
composedPredicate.test(taskData);
is run using a not-fully initialized instance and your code has random unexpected errors in production resulting in great losses to your company (potentially that happens .. depends on the system that you're building)Whether or not the reordering of step 4 and 5 happens depends on many factors. Maybe it only does under heavy system load. It may not happen at all on your OS, hardware, version of JVM, etc. (But on the next version of the JVM, your OS, or when you move your application to a different physical machine, it may suddenly start happening)
Bad idea.
Upvotes: 2