Computers have been my hobby since I was 12. Now I'm a freelance Java developer. Like many other developers I am working on various private projects. Some are open source components (Butterfly Components - DI container, web ui, persistence api, mock test api etc.). Some are the tutorials at tutorials.jenkov.com. Yet others are web projects. I hold a bachelor degree in computer science and a master degree in IT focused on P2P networks. Jakob has posted 35 posts at DZone. You can read more from them at their website. View Full User Profile

Java Concurrency: Read / Write Locks

06.09.2008
| 39321 views |
  • submit to reddit

Jakob has done a great series on Java Concurrency - check out the first 14 articles at his blog. Going forward, we're delighted to announce that you'll also be able to follow the series here on JavaLobby.

A read / write lock is more sophisticated lock than the Lock implementations shown in the text Locks in Java. Imagine you have an application that reads and writes some resource, but writing it is not done as much as reading it is. Two threads reading the same resource does not cause problems for each other, so multiple threads that want to read the resource are granted access at the same time, overlapping. But, if a single thread wants to write to the resource, no other reads nor writes must be in progress at the same time. To solve this problem of allowing multiple readers but only one writer, you will need a read / write lock.

Java 5 comes with read / write lock implementations in the java.util.concurrent package. Even so, it may still be useful to know the theory behind their implementation.

Here is a list of the topics covered in this text:

  1. Read / Write Lock Java Implementation
  2. Read / Write Lock Reentrance
  3. Read Reentrance
  4. Write Reentrance
  5. Read to Write Reentrance
  6. Write to Read Reentrance
  7. Fully Reentrant Java Implementation
  8. Calling unlock() from a finally-clause

Read / Write Lock Java Implementation

First let's summarize the conditions for getting read and write access to the resource:

Read Access If no threads are writing, and no threads have requested write access.
Write Access If no threads are reading or writing.

If a thread wants to read the resource, it is okay as long as no threads are writing to it, and no threads have requested write access to the resource. By up-prioritizing write-access requests we assume that write requests are more important than read-requests. Besides, if reads are what happens most often, and we did not up-prioritize writes, starvation could occur. Threads requesting write access would be blocked until all readers had unlocked the ReadWriteLock. If new threads were constantly granted read access the thread waiting for write access would remain blocked indefinately, resulting in starvation. Therefore a thread can only be granted read access if no thread has currently locked the ReadWriteLock for writing, or requested it locked for writing.

A thread that wants write access to the resource can be granted so when no threads are reading nor writing to the resource. It doesn't matter how many threads have requested write access or in what sequence, unless you want to guarantee fairness between threads requesting write access.

With these simple rules in mind we can implement a ReadWriteLock as shown below:

public class ReadWriteLock{

private int readers = 0;
private int writers = 0;
private int writeRequests = 0;

public synchronized void lockRead() throws InterruptedException{
while(writers > 0 || writeRequests > 0){
wait();
}
readers++;
}

public synchronized void unlockRead(){
readers--;
notifyAll();
}

public synchronized void lockWrite() throws InterruptedException{
writeRequests++;

while(readers > 0 || writers > 0){
wait();
}
writeRequests--;
writers++;
}

public synchronized void unlockWrite() throws InterruptedException{
writers--;
notifyAll();
}
}

The ReadWriteLock has two lock methods and two unlock methods. One lock and unlock method for read access and one lock and unlock for write access.

The rules for read access are implemented in the lockRead() method. All threads get read access unless there is a thread with write access, or one or more threads have requested write access.

The rules for write access are implemented in the lockWrite() method. A thread that wants write access starts out by requesting write access (writeRequests++). Then it will check if it can actually get write access. A thread can get write access if there are no threads with read access to the resource, and no threads with write access to the resource. How many threads have requested write access doesn't matter.

It is worth noting that both unlockRead() and unlockWrite() calls notifyAll() rather than notify(). To explain why that is, imagine the following situation:

Inside the ReadWriteLock there are threads waiting for read access, and threads waiting for write access. If a thread awakened by notify() was a read access thread, it would be put back to waiting because there are threads waiting for write access. However, none of the threads awaiting write access are awakened, so nothing more happens. No threads gain neither read nor write access. By calling noftifyAll() all waiting threads are awakened and check if they can get the desired access.

Calling notifyAll() also has another advantage. If multiple threads are waiting for read access and none for write access, and unlockWrite() is called, all threads waiting for read access are granted read access at once - not one by one.


Read / Write Lock Reentrance

The ReadWriteLock class shown earlier is not reentrant. If a thread that has write access requests it again, it will block because there is already one writer - itself. Furthermore, consider this case:

  1. Thread 1 gets read access.
  2. Thread 2 requests write access but is blocked because there is one reader.
  3. Thread 1 re-requests read access (re-enters the lock), but is blocked because there is a write request

In this situation the previous ReadWriteLock would lock up - a situation similar to deadlock. No threads requesting neither read nor write access would be granted so.

To make the ReadWriteLock reentrant it is necessary to make a few changes. Reentrance for readers and writers will be dealt with separately.

 

References
Published at DZone with permission of its author, Jakob Jenkov. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Jakob Jenkov replied on Wed, 2008/06/11 - 4:31pm in response to: David Karr

Hmm... seems like it... I'll have to check what Holub wrote when I get near the book again.

Anyways, this just goes to show how many details there are to get 100% straight in Java Concurrency. That is why I am writing the trail -  as notes for myself and fellow developers. I have worked a reasonable amount with Java concurrency, but I am no where near the expertise of Doug Lea, Allen Holub, Brian Goetz etc. Any comments like this article has received are appreciated, and the texts in the tutorial will be updated to reflect any corrections pointed out.

Slava Imeshev replied on Wed, 2008/06/11 - 5:01pm in response to: Jakob Jenkov

[quote=jj83777]

Any comments like this article has received are appreciated, and the texts in the tutorial will be updated to reflect any corrections pointed out.

[/quote]

 

Yes, it does demonstrate that multithreading is hard to do right.

 As a friendly suggestion, I'd put a banner or a comment in the code stating that this code is to demonstrate approaches and should not be considered production quality. People tend to consider any cut'n'paste code that complies a production quiality and this one is not. Right now it does not keep track of nested reads deeper than one which will cause a self-deadlock.

 

Slava

Jakob Jenkov replied on Wed, 2008/06/11 - 5:08pm

Hi Slava, I am working on the correction as we speek which will correct the nested read and write lock errors. I'll post a comment when its ready for you to look at.

David Karr replied on Wed, 2008/06/11 - 5:37pm in response to: Slava Imeshev

So Slava, if an expert like you misses the fact that the nested read problem has already been noted and discussed (see the earlier comments), what hope do we have that beginners will read a boilerplate statement about "production quality" and take it to heart? ;)

Slava Imeshev replied on Wed, 2008/06/11 - 5:57pm in response to: David Karr

David,

You are right. The priority of spotting this problem belongs to you, indeed :)

I assume double-quoting the production quality bears certain irony which I fail to recognize, just as the references to beginners and experts :) To make sure we are on the same page, that's what I meant: http://tutor2u.net/business/gcse/production_quality_introduction.htm

To get practical and given that the promised fix is still not there, what would be your suggestions regarding addressing the nested read problem?

Slava

Cacheonix - Clustered Cache and Data Grid for Java

Jakob Jenkov replied on Wed, 2008/06/11 - 6:02pm

Impatience is a virtue ;-)

 

The updates are ready now, here on JavaLobby. I'll repost them on my own blog either later today or tomorrow. 

Slava Imeshev replied on Wed, 2008/06/11 - 6:17pm in response to: Jakob Jenkov

[quote=jj83777]

The updates are ready now, here on JavaLobby. I'll repost them on my own blog either later today or tomorrow.

[/quote]

 

Awesome! Do you think you could get rid of put access here?

 

else { readingThreads.put(callingThread, (accessCount -1)); }  

 

Slava

Jakob Jenkov replied on Thu, 2008/06/12 - 12:19am

It is probably possible to optimize the design for performance if you want to. For instance, if the read access counter was a class rather than just an int, you could just update the counter. For instance:

 

 

public class AccessCount{
  public int count = 0;
}

Then in the lockRead() and unlockRead() you could do something like this:

AccessCount accessCount = getReadAccessCount(callingThread);

if(accessCount == null){
  accessCount = new AccessCount();
  readingThreads.put(callingThread, accessCount);
}
accessCount.count++;

 

 





AccessCount accessCount = getReadAccessCount(callingThread);
if(accessCount.count == 1){
  readingThreads.remove(callingThread);
} else {
  accessCount.count--;
}

Slava Imeshev replied on Thu, 2008/06/12 - 2:59am

 

Exactly! Great job, Jacob!

Slava 

Vamsi Krishna replied on Sun, 2014/11/02 - 7:18am

Its really very nice thanks for the information ssc results 2015 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.