223. ReentrantReadWriteLock

Typically, a read-write tandem (for example, read-write a file) should be accomplished based on two statements:

  • Readers can read simultaneously as long as there are no writers (shared pessimistic lock).

  • A single writer can write at a time (exclusive/pessimistic locking).

The following diagram depicts readers on the left-hand side and writers on the right-hand side:

Mainly, the following behavior is implemented by ReentrantReadWriteLock:

  • Provides pessimistic locking semantics for both locks (read and write lock).

  • If some readers hold the read lock and a writer wants the write lock, then no more readers are allowed to acquire the read lock until the writer released the write lock.

  • A writer can acquire the read lock, but a reader cannot acquire the write lock.

In case of non-fair locks, the order in which threads are granted access is unspecified. If the lock should be fair (give precedence to the thread that has been waiting for the longest), then use the ReentrantReadWriteLock​(boolean fair) constructor.

The idiom for using ReentrantReadWriteLock is shown as follows:

ReadWriteLock / ReentrantReadWriteLock lock 
= new ReentrantReadWriteLock();
...
lock.readLock() / writeLock().lock();
try {
...
} finally {
lock.readLock() / writeLock().unlock();
}

The following code represents a ReentrantReadWriteLock usage case that reads and writes an integer amount variable:

public class ReadWriteWithLock {

private static final Logger logger
= Logger.getLogger(ReadWriteWithLock.class.getName());
private static final Random rnd = new Random();

private static final ReentrantReadWriteLock lock
= new ReentrantReadWriteLock(true);

private static final Reader reader = new Reader();
private static final Writer writer = new Writer();

private static int amount;

private static class Reader implements Runnable {

@Override
public void run() {
if (lock.isWriteLocked()) {
logger.warning(() -> Thread.currentThread().getName()
+ " reports that the lock is hold by a writer ...");
}

lock.readLock().lock();

try {
logger.info(() -> "Read amount: " + amount
+ " by " + Thread.currentThread().getName());
} finally {
lock.readLock().unlock();
}
}
}

private static class Writer implements Runnable {

@Override
public void run() {
lock.writeLock().lock();
try {
Thread.sleep(rnd.nextInt(2000));
logger.info(() -> "Increase amount with 10 by "
+ Thread.currentThread().getName());

amount += 10;
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
logger.severe(() -> "Exception: " + ex);
} finally {
lock.writeLock().unlock();
}
}
...
}

And, let's perform 10 reads and 10 writes with two readers and four writers:

ExecutorService readerService = Executors.newFixedThreadPool(2);
ExecutorService writerService = Executors.newFixedThreadPool(4);

for (int i = 0; i < 10; i++) {
readerService.execute(reader);
writerService.execute(writer);
}

A possible output will be as follows:

[09:09:25] [INFO] Read amount: 0 by pool-1-thread-1
[09:09:25] [INFO] Read amount: 0 by pool-1-thread-2
[09:09:26] [INFO] Increase amount with 10 by pool-2-thread-1
[09:09:27] [INFO] Increase amount with 10 by pool-2-thread-2
[09:09:28] [INFO] Increase amount with 10 by pool-2-thread-4
[09:09:29] [INFO] Increase amount with 10 by pool-2-thread-3
[09:09:29] [INFO] Read amount: 40 by pool-1-thread-2
[09:09:29] [INFO] Read amount: 40 by pool-1-thread-1
[09:09:31] [INFO] Increase amount with 10 by pool-2-thread-1
...
Before deciding to rely on ReentrantReadWriteLock, please consider that it may suffer from starvation (for example, when writers are given priority, readers might be starved). Moreover, we could not upgrade a read lock to a write lock (downgrading from writer to reader is possible), and there is no support for optimistic reads. If any of this matters for you then consider StampedLock, which we will look at in the next problem.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.14.98