In this article, we will examine the subtleties of using newly introduced Lock classes in java.util.concurrent.locks package and their advantages over traditional "synchronized" blocks and methods.
The fact that a synchronized block of code is blocking in nature is one of the primary limitations. If two threads A and B call a consume and produce methods which act on the same resource, the second thread will block till the first one releases the lock. This limitation however provided a simplified way of writing multi-threaded code in Java, very helpful for beginners to understand and use when contrasted with POSIX pthread library in C/C++.
However, synchronized keyword does not provide a mechanism to check if another thread has obtained the lock or any means to attempt to lock. The wait/notify construct provides a means to coordinate between producer/consumer threads but one of them has to still wait, doing nothing, till it is notified.
Following code snippet shows a simple,non-optimized resource repository guarded by synchronized methods.
Code snippet 1.
- class ResourceRepositary {
- private ArrayList<String> resourceRepositary= new ArrayList<String>();
- public synchronized String consume() {
- String resource=null;
- if ( this.resourceRepositary.size()>0 )
- {
- resource =this.resourceRepositary.remove(this.resourceRepositary.size()-1);
- }
- return resource;
- }
- public synchronized void produce(String resource) throws InterruptedException{
- this.resourceRepositary.add(resource);
- System.out.println("Blocking for 100 seconds");
- System.out.flush();
- Thread.sleep(100000);//sleep for 100 seconds
- System.out.println("UnBlocking ");
- }
- }
If produce() and consume() methods are called from different threads, consume will block till produce() exits. ( assuming produce() is executed first ). The attached code sample has a method blockingExample() depicting this behavior.
Whereas changing the Resource repository to use RentrantLock instead of synchronized block will prevent the consume() from blocking. Thread executing consume() will not block, it will attempt to obtain the lock and will be unsuccessful if it's held by another thread. Agreed, this example isn't an effective example of the advantage we gain by tryLock(), but nevertheless conveys the idea.
Let's take another look at line 25-27 in code snippet 2. When we use Lock classes, we will have to follow this idiom of unlocking in the finally block to ensure unlocking. Folks from C++ will say deja vu, reminiscing "unlocking the mutex in the destructor" idiom.
Code Snippet 2
- class ResourceRepositaryWithLock extends ResourceRepositary {
- private Lock lock=new ReentrantLock();
- public String consume() {
- String resource=null;
- if (lock.tryLock()!=true )
- {
- return null;
- }
- if ( this.resourceRepositary.size()>0 )
- {
- resource =this.resourceRepositary.remove(this.resourceRepositary.size()-1);
- }
- return resource;
- }
- public void produce(String resource) throws InterruptedException{
- try {
- this.lock.lock();
- this.resourceRepositary.add(resource);
- System.out.println("Blocking for 100 seconds");
- System.out.flush();
- Thread.sleep(100000);// sleep for 100 seconds
- System.out.println("UnBlocking ");
- } finally {
- this.lock.unlock();
- }
- }
- }
By using RentrantLock instance directly instead of Lock interface, we can access additional functionality in ReEntrantLock. ReEntrantLock has methods to find out the number of locks held.
As you might have expected, a thread can acquire the same lock multiple times ( definition of reentrant) but can unlock the lock completely with a single call to unlock().
Code Snippet 3
- class ResourceRepositaryWithRentrantLock extends ResourceRepositary {
- private ReentrantLock reentrantLock=new ReentrantLock();
- public String consume() {
- String resource = null;
- try {
- if (reentrantLock.tryLock() != true)
- {
- int numberOfWaitingThreads = reentrantLock.getQueueLength();
- System.out.println("NoOfWaitingThreads: "+ numberOfWaitingThreads);
- if (numberOfWaitingThreads < 5)
- reentrantLock.lock();
- System.out.println("Do I own it"+ reentrantLock.isHeldByCurrentThread() );
- System.out.println("Number of Locks owned by current Thread:"+ reentrantLock.getHoldCount());
- reentrantLock.lock(); //thread holding the lock can obtain the lock again.
- reentrantLock.lock(); //HoldCount will incremement with each lock() call.
- System.out.println("Number of Locks owned by current Thread:"+ reentrantLock.getHoldCount());
- }
- if (this.resourceRepositary.size() > 0) {
- resource = this.resourceRepositary
- .remove(this.resourceRepositary.size() - 1);
- }
- } finally {
- int lockCount=0;
- while( reentrantLock.isLocked() )
- {
- reentrantLock.unlock(); //one unlock unlocks all the locks.
- System.out.println("unlock count:"+ ++lockCount);
- }
- }
- return resource;
- }
- public void produce(String resource) throws InterruptedException{
- try {
- this.reentrantLock.lock();
- this.resourceRepositary.add(resource);
- System.out.println("Blocking for 100 seconds");
- System.out.flush();
- Thread.sleep(10000);// sleep for 100 seconds
- System.out.println("UnBlocking ");
- } finally {
- this.reentrantLock.unlock();
- }
- }
- }
We can also use condition variables associated with the ReEntrantLock. With wait(), notifiy(), we were limited to one condition per lock object. With ReentrantLock, we can associate multiple conditions with the same lock object.
In code snippet 4, we have extended ResourceRepositaryWithRentrantLock to provide implementation of consume() and produce() that use conditions associated with reentrantLocks.
Code Snippet 4
- private Condition resourceNotEmpty=this.reentrantLock.newCondition();
- @Override
- public void produce(String resource) throws InterruptedException{
- try {
- this.reentrantLock.lock();
- this.resourceRepositary.add(resource);
- System.out.println("signalling the fullfillment for condition:"+ resourceNotEmpty);
- resourceNotEmpty.signalAll();
- System.out.println("UnBlocking ");
- } finally {
- this.reentrantLock.unlock();
- }
- }
- @Override
- public String consume() {
- String resource = null;
- try {
- reentrantLock.lock();
- while (this.resourceRepositary.size() == 0)
- {
- this.resourceNotEmpty.await();//waiting for a condition to be true.
- resource = this.resourceRepositary.remove(this.resourceRepositary.size() - 1);
Additionally Condition class provides methods to get into uniterruptable wait states, timed wait states etc.
This API javadocs provide good explantion. http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/locks/Condi...
Resources:
Brian Goetz's "Java Concurrency in Practise"
http://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/032134960...
Code download:
The entire code can be downloaded from the attachment below. It has been compiled and run using Java 1.5.03. The code sample has additional comments and print statements, which will help us grasp the concepts. I would strongly recommend walking through the code along with reading the article.
In the next article, we will tackle Atomic variables in the package java.util.concurrent.atomic
Thanks and keep checking www.techgrasp.com













Isn't it supposed to be as follows,
lock()being beforetry, because you only need tounlock();iflock()succeeded:I'm not sure whether
unlock()may be called when the current thread does not have the lock. The Javadocs don't say anything about that.The semantics for read/write lock upgrading are a lot more "fun" though, here's an example for initializing a cache (e.g. a
HashMap) on demand:Post new comment