6

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.

Suzan Cioc
  • 29,281
  • 63
  • 213
  • 385
  • You might find the Disruptor library interesting as it solves this sort of problem well. http://code.google.com/p/disruptor/ – Peter Lawrey Oct 26 '12 at 14:03
  • blocking queues *do* match the task as it is explained. Why do you mean write data in all cases? You can setup so that writing never fail. – UmNyobe Oct 26 '12 at 14:26
  • I've tweaked my answer @UmNyobe. Initially it didn't handle the case that she wants to throw away the oldest data to make space in the queue. The blocking queue should now work with the changes. – Gray Oct 26 '12 at 14:34
  • @UmNyobe task was not explained in full. The question is about `ReentrantReadWriteLock` not about how to solve the task. – Suzan Cioc Oct 26 '12 at 14:38
  • Strange to not being looking for a solution but whatever. One thing to consider @SuzanCioc is that if you are doing IO, you are most likely wasting time prematurely optimizing the locking that will not be necessary since your application is going to be IO bound. – Gray Oct 26 '12 at 14:43
  • @Gray I looked for solution and chosen it already. – Suzan Cioc Oct 26 '12 at 14:48
  • @SuzanCioc Granted your example is psudeo, do you plan on unlocking the `writeLock` between the two `signalAll()`? Or would it be after the two `signalAll()` – John Vint Oct 26 '12 at 15:13

4 Answers4

7

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.

GeertPt
  • 16,398
  • 2
  • 37
  • 61
  • But if I acquire `writeLock` too, then how do I benefit from separate locks? Wouldn't it better to have one lock then? – Suzan Cioc Oct 26 '12 at 15:08
  • You only acquire the writeLock when there's no data, and you release it implicitly while awaiting the condition. As long as there's data, multiple readers will read it simultaneously. – GeertPt Oct 26 '12 at 15:11
3

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.

Gray
  • 115,027
  • 24
  • 293
  • 354
  • 1
    Indeed BlockingQueue is ideal for this issue. Forgot about it. – UmNyobe Oct 26 '12 at 14:13
  • First, I am sending binary data of different sizes and at high speed. Second, `queue.offer()` won't wait, but also won't write any data. I need to write data at all cases. – Suzan Cioc Oct 26 '12 at 14:22
  • So binary data of different sizes is fine. It can be a queue of `byte[]` for example. Right. I've edited my answer to take account that you always want to add to the queue @SuzanCioc. – Gray Oct 26 '12 at 14:27
  • Queue don't match. I am using interfaces like `InputStream` and `OutputStream` so it is hard to implement interboundary reads (but I did it in previous version). Anyway the task is described in very brief, just to clarify the main question. Task information is insufficient to improve entire solution. – Suzan Cioc Oct 26 '12 at 14:36
  • I still don't get why a queue wouldn't work. An input stream gives you a `byte[]`. I don't see how the `ReentrantReadWriteLock` saves you from the interboundary read issue @SuzanCioc. – Gray Oct 26 '12 at 14:41
  • @Gray okay, queues are great but are not the topic of my question. – Suzan Cioc Oct 26 '12 at 14:43
0

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.

UmNyobe
  • 22,539
  • 9
  • 61
  • 90
  • By "no waiting" I meant don't wait for condition. But of course I need SOME little waiting for consistency. – Suzan Cioc Oct 26 '12 at 14:26
0

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

Amit Deshpande
  • 19,001
  • 4
  • 46
  • 72