It is said, that ReentrantReadWriteLock
is intended for one writer and multiple readers.
Nevertheless, readers should wait until some data is present in the buffer.
So, what to lock?
I created concurrency objects like follows:
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
protected final Lock readLock = rwl.readLock();
protected final Lock writeLock = rwl.writeLock();
protected final Condition hasData = writeLock.newCondition();
now in write method I do:
writeLock.lock();
// writing first portion and updating variables
hasData.signalAll();
// if required then writing second portion and updating variables
hasData.signalAll();
But how to write a reader? Should it acquire only readLock
? But how it can wait for a signal then? If it aquires also a writeLock
then where is the supremacy fo read/write locking?
How to ensure required variables will not change during reading if they are protected only by writeLock
?
QUEUES DON'T MATCH THE TASK
This is the question about ReentrantReadWriteLock
.
The ReentrantReadWriteLock is indeed a bit confusing because the readLock doesn't have a condition. You have to upgrade to a writeLock in your reader only to wait for the condition.
In the writer.
writeLock.lock(); //locks all readers and writers
// do write data
hasData.signalAll();
writeLock.unlock();
In reader you do:
readLock.lock(); //blocks writers only
try{
if(!checkData()) //check if there's data, don't modify shared variables
{
readLock.unlock();
writeLock.lock(); // need to lock the writeLock to allow to use the condition.
// only one reader will get the lock, other readers will wait here
try{
while(!checkData()) // check if there' still no data
{
hasData.await(); //will unlock and re-lock after writer has signalled and unlocked.
}
readLock.lock(); // continue blocking writer
}
finally
{
writeLock.unlock(); //let other readers in
}
}
//there should be data now
readData(); // don't modify variables shared by readers.
}
finally
{
readlock.unlock(); //let writers in
}
For completeness, each unlock() should be in a finally block, of course.
But how to write a reader? Should it acquire only readLock? But how it can wait for a signal then? If it aquires also a writeLock then where is the supremacy fo read/write locking?
I'd switch to using a BlockingQueue
that will take care of all of this for you. Your readers can call queue.take()
which blocks waiting for there to be elements in the queue.
Your writer is a bit more complicated. What I'd do is something like the following:
// initially try to put an element into the queue
if (!queue.offer(element)) {
// if the queue is full then take an element off the head and just drop it
// this won't block and may not remove anything due to race conditions
queue.poll();
// this put will never block because now there will be space in the queue
queue.put(element);
}
This won't work if there are multiple writers. You'd need a synchronized
lock then. If you are dealing with a fixed size queue then the ArrayBlockingQueue
should work well.
You cannot achieve a non-blocking behavior using primitives which have a blocking behavior. If you really want the writer to "writes and never waits for anybody", he should not even know the locks you mentionned exists.
When you executes
rwl.writeLock().lock();
The writer will wait if there are readers operating.
You should try to use wait-free (at least lock-free) primitives if you want to respect the "never wait" condition. For instance, use a ConcurrentLinkedQueue and a locking mechanism which will be only used to manage the race conditions between the readers.
What you need is LinkedBlockingQueue
which provides two separate locks takeLock
and putLock
.
Offer
,put
method of the queue always use putLock
where as take
method always use takeLock
来源:https://stackoverflow.com/questions/13088363/how-to-wait-for-data-with-reentrantreadwritelock