Saturday, January 26, 2008

ReadWriteLock example in Java

Writing multithreaded java applications is not a piece of cake. Extra care must be taken because bad synchronization can bring your application to its knees. The JVM heap is shared by all the threads. If multiple threads need to use the same objects or static class variables concurrently, thread access to shared data must be carefuly managed. Since version 1.5, utility classes commonly useful in concurrent programming is included in the JSDK.

In Java synchronized keyword is used to acquire a exclusive lock on an object. When a thread acquires a lock of an object either for reading or writing, other threads must wait until the lock on that object is released. Think of a scenerio that there are many reader threads that reads a shared data frequently and only one writer thread that updates shared data. It's not necessary to exclusively lock access to shared data while reading because multiple read operations can be done in parallel unless there is a write operation.

In this post i'll give an example usage of ReadWriteLock interface which is introduced in the Java 1.5 API Doc. In Java Api Documentation it says :

A ReadWriteLock maintains a pair of associated locks,
one for read-only operations and one for writing.
The read lock may be held simultaneously by multiple reader threads,
so long as there are no writers. The write lock is exclusive.

Reader threads can read shared data simultaneously. A read operation does not block other read operations. This is the case when you execute an SQL SELECT statement. But write operation is exclusive. This means all readers and other writers are blocked when a writer thread holds the lock for modifing shared data.


Writer.java This class represents a thread that updates shared data. Writer uses WriteLock of ReadWriteLock to exclusively lock access to dictionary.


01 package deneme.readwritelock;
02 
03 
04 public class Writer extends Thread{
05   private boolean runForestRun = true;
06   private Dictionary dictionary = null;
07   
08   public Writer(Dictionary d, String threadName) {
09     this.dictionary = d;
10     this.setName(threadName);
11   }
12   @Override
13   public void run() {
14     while (this.runForestRun) { 
15       String [] keys = dictionary.getKeys();
16       for (String key : keys) {
17         String newValue = getNewValueFromDatastore(key);
18         //updating dictionary with WRITE LOCK
19         dictionary.set(key, newValue);
20       }
21       
22       //update every seconds
23       try {
24         Thread.sleep(1000);
25       catch (InterruptedException e) {
26         e.printStackTrace();
27       }
28     }
29   }
30   public void stopWriter(){
31     this.runForestRun = false;
32     this.interrupt();
33   }
34   public String getNewValueFromDatastore(String key){
35     //This part is not implemented. Out of scope of this artile
36     return "newValue";
37   }
38 }



Reader.java This class represents a thread that reads share data.


01 package deneme.readwritelock;
02 
03 public class Reader extends Thread{
04   
05   private Dictionary dictionary = null;
06   public Reader(Dictionary d, String threadName) {
07     this.dictionary = d;
08     this.setName(threadName);
09   }
10   
11   private boolean runForestRun = true;
12   @Override
13   public void run() {
14     while (runForestRun) {
15       String [] keys = dictionary.getKeys();
16       for (String key : keys) {
17         //reading from dictionary with READ LOCK
18         String value = dictionary.get(key);
19         
20         //make what ever you want with the value.
21         System.out.println(key + " : " + value);
22       }
23       
24       //update every seconds
25       try {
26         Thread.sleep(1000);
27       catch (InterruptedException e) {
28         e.printStackTrace();
29       }
30     }
31   }
32   
33   public void stopReader(){
34     this.runForestRun = false;
35     this.interrupt();
36   }
37 }



Dictionary.java This is a simple and thread safe dictionary. Read operations are managed through ReadLock and write operations (updates) are managed throuh WriteLock.


01 package deneme.readwritelock;
02 
03 import java.util.HashMap;
04 import java.util.concurrent.locks.Lock;
05 import java.util.concurrent.locks.ReentrantReadWriteLock;
06 
07 public class Dictionary {
08   
09   private final ReentrantReadWriteLock readWriteLock = 
10     new ReentrantReadWriteLock();
11 
12   private final Lock read  = readWriteLock.readLock();
13   
14   private final Lock write = readWriteLock.writeLock();
15   
16   private HashMap<String, String> dictionary = new HashMap<String, String>();
17   
18   public void set(String key, String value) {
19     write.lock();
20     try {
21       dictionary.put(key, value);
22     finally {
23       write.unlock();
24     }
25   }
26   
27   public String get(String key) {
28     read.lock();
29     try{
30       return dictionary.get(key);
31     finally {
32       read.unlock();
33     }
34   }
35 
36   public String[] getKeys(){
37     read.lock();
38     try{
39       String keys[] new String[dictionary.size()];
40       return dictionary.keySet().toArray(keys);
41     finally {
42       read.unlock();
43     }
44   }
45   
46   public static void main(String[] args) {
47     Dictionary dictionary = new Dictionary();
48     dictionary.set("java",  "object oriented");
49     dictionary.set("linux""rulez");
50     Writer writer  = new Writer(dictionary, "Mr. Writer");
51     Reader reader1 = new Reader(dictionary ,"Mrs Reader 1");
52     Reader reader2 = new Reader(dictionary ,"Mrs Reader 2");
53     Reader reader3 = new Reader(dictionary ,"Mrs Reader 3");
54     Reader reader4 = new Reader(dictionary ,"Mrs Reader 4");
55     Reader reader5 = new Reader(dictionary ,"Mrs Reader 5");
56     writer.start();
57     reader1.start();
58     reader2.start();
59     reader3.start();
60     reader4.start();
61     reader5.start();
62   }
63   
64 }

24 comments:

Taylor said...

Cool post - people should definitely know more about Java 1.5 advanced concurrency controls.

I just blogged about a related topic - basically showing how Terracotta provides read/write lock semantics in a single JVM - or multiple JVMs - using both synchronized (something ordinary Java cannot do) or ReentrantReadWriteLocks.

If you're interested the blog is here:

Stupid JVM Tricks - Read Lock From Just Synchronized

Anonymous said...

Shouldn't the method to stop the reader thread be stopReader() and not stopWriter() :)

İlkin Ulaş BALKANAY said...

You are rigth, just a copy&paste mistake ;) i'll fix it.

Anonymous said...

Thank you for the great example. Nice to see people still take the time to teach one another.

Anonymous said...

Nice example.

Not sure, but shouldn't runForestRun be volatile (at least, memory-synchronized somehow)? It's read in one thread (calling run) and written in another (calling stopReader/Writer).

İlkin Ulaş BALKANAY said...

I don't see any reason to make variable "runForestRun" volatile. Thread will read its local variable every seconds to determine its termination. Access to variable "runForestRun" can be made synchronized but it is not necessary in this example.

Anonymous said...

Well written article.

Anonymous said...

can you provide a code for getNewValueFromDatastore method, please ?

İlkin Ulaş BALKANAY said...

It can return a random string of variable length. The implementation of method getNewValueFromDatastore is not very important. Why do you need code for that method?

Anonymous said...

i'm trying to understand how it works and what's the purpose of it...
1. can u tell me how can i implement those stop-methods (theoretically.. an example)? ...cuz now it's just an unding loop
2. what's the advantage of using locks in this example and not a normal synchronization?

thx in advance!

İlkin Ulaş BALKANAY said...

With syncronized keyword we can make access to read and write methods synchonized.
If there isn't any write operation, two reader threads do not need to wait each other. They can read the value without any synchronization (unless any writer thread is updating the value.)
A read-write lock guarantees all reader threads read the same (the most up-to-date value) value while preventing synchronization overhead(in case where there is not any write operation)

Anonymous said...

nice post

Anonymous said...

Great information, thank you for this nice post.

Anonymous said...

It was rather interesting for me to read the post. Thanx for it. I like such themes and everything that is connected to this matter. I would like to read more soon.

Free Web Directory said...

I really like your Blog.. Bravo!

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Pedro Garcia Inácio said...

Thanks. Simple examples, simple to understand.

Unknown said...

Very good example.
Thank you

Unknown said...

Very nice Example,
Thank you

Vijay said...

Gr8 Example! Thanks.

Anurag said...

Good example

Ramesh Arangot said...

Thank you. Very nice example.

calyan kumar said...

hi,

Shouldn't the call to lock()in set and get methods be inside try block?