Let's imagine a situation when this shared object is in a 3rd party library. In our example this object is a Book class that I used in previous posts.
Most of the time we want to show information about the book, but exceptionally the author modifies the content and therefore the number of pages needs to be changed.
Using synchronized for the methods that read/write the book would be a poor solution.
Instead, we could use ReadWriteLock which allows to differentiate between read and write operations. Read operation is concurrent, which means that all the threads have access to the object. Only write operation is exclusive and blocks other threads.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ReadWriteLockExample { | |
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); | |
private Book book = new Book("Haruki Murakami", "1Q84", 300); | |
public void showInfoAboutBook() { | |
readWriteLock.readLock().lock(); | |
try { | |
System.out.println("Reading book info from thread " + Thread.currentThread().getName()); | |
System.out.println(book); | |
} finally { | |
readWriteLock.readLock().unlock(); | |
} | |
} | |
public void setNumberOfPages(int pages) { | |
readWriteLock.writeLock().lock(); | |
try { | |
System.out.println("Setting book property from thread " + Thread.currentThread().getName()); | |
book.setPages(pages); | |
System.out.println("New value for book has been set"); | |
} finally { | |
readWriteLock.writeLock().unlock(); | |
} | |
} | |
} |
Let's see it in action:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ReadWriteLockExampleRunner { | |
public static void main(String[] args) { | |
final ReadWriteLockExample example = new ReadWriteLockExample(); | |
ExecutorService executor = Executors.newFixedThreadPool(10); | |
Runnable readTask = new Runnable() { | |
@Override | |
public void run() { | |
example.showInfoAboutBook(); | |
} | |
}; | |
Runnable writeTask = new Runnable() { | |
@Override | |
public void run() { | |
example.setNumberOfPages(301); | |
} | |
}; | |
for (int i = 0; i < 100; i++) { | |
executor.submit(readTask); | |
if (i % 10 == 0) { | |
executor.submit(writeTask); | |
} | |
} | |
executor.shutdown(); | |
} | |
} |
The example with the book is purely educational and not too realistic. In real life we would probably have some kind of a large collection. It's good to read ReentrantReadWriteLock and ReadWriteLock javadoc, because sometimes using them may bring more overhead than mutual exclusion.
No comments:
Post a Comment