I've written a Java ReadWriteLock where the readers use double-checked locking to acquire the write-lock. Is this unsafe (as is the case for DCL with lazy-instantiation)?
import java.util.concurrent.atomic.AtomicInteger;
public class DCLRWLock {
private boolean readerAcquiringWriteLock = false;
private boolean writerLock = false;
private AtomicInteger numReaders = new AtomicInteger();
public void readerAcquire() throws InterruptedException {
while (!nzAndIncrement(numReaders)) {
synchronized (this) {
if (numReaders.get() != 0)
continue;
if (readerAcquiringWriteLock) {
do {
wait();
} while (readerAcquiringWriteLock);
} else {
readerAcquiringWriteLock = true;
writerAcquire();
readerAcquiringWriteLock = false;
assert numReaders.get() == 0;
numReaders.set(1);
notifyAll();
break;
}
}
}
}
public void readerRelease() {
if (numReaders.decrementAndGet() == 0)
writerRelease();
}
public synchronized void writerAcquire() throws InterruptedException {
while (writerLock)
wait();
writerLock = true;
}
public synchronized void writerRelease() {
writerLock = false;
notifyAll();
}
// Atomically:
// If x is nonzero, increments x and returns true
// Otherwise returns false
private static boolean nzAndIncrement(AtomicInteger x) {
for (;;) {
int val = x.get();
if (val == 0)
return false;
else if (x.compareAndSet(val, val + 1))
return true;
}
}
}
I know that Java already has a ReentrantReadWriteLock. I'm more interested in the general question of how to determine what forms of DCL are or aren't safe?
The unsafety of the DCL comes about when we assume that just because we read a non-null reference from a shared variable, all the writes by the thread which wrote the reference will be visible. In other words, we read a reference published via a datarace and assume things will work out fine.
In your case you don't even have a data race, but just a race condition on an atomic variable. Therefore the non-safety described above certainly does not apply here.