Saturday, 18 June 2011

Java Locks : Re-entrant locks

Java concurrency library provides more control over synchronization than synchronized. Either we need to control types of access (read and write) separately, or it is cumbersome to use because either there is no obvious mutex or we need to maintain multiple mutexes. So doing it by synchronized will eat lot of time and will be buggy as well.
Thankfully, lock utility classes were added in Java 1.5 and make these problems easier to solve.

Java Reentrant Locks


Java has a few lock implementations in the java.util.concurrent.locks package.
The general classes of locks are nicely laid out as interfaces:
  • Lock - the simplest case of a lock which can be acquired and released
  • ReadWriteLock - a lock implementation that has both read and write lock types – multiple read locks can be held at a time unless the exclusive write lock is held
Java provides two implementations of these locks that we care about – both of which are reentrant (this just means a thread can reacquire the same lock multiple times without any issue).
  • ReentrantLock - as you’d expect, a reentrant Lock implementation
  • ReentrantReadWriteLock - a reentrant ReadWriteLock implementation

Extended capabilities with Reentrant locks

The ReentrantLock in the util.concurrent.locks package gives the developers some flexibility here. With ReentrantLock following are some of the options

  1. tryLock() : With ReentrantLock, the thread can immediately return if it did not get the lock (if the lock is with some other thread).
  2. tryLock(long timeout, TimeUnit unit):With ReentrantLock, the thread can wait for some duration to get hold of the lock. If it does not get the lock within some time, it will return.
  3. lockInterruptibly() : With ReentrantLock, the thread waiting for the lock can be interrupted and cause it to come out with InterruptedException.
Now, let’s see some examples. So the general way to use re-entrant lock is like this :
final ReentrantLock _lock = new ReentrantLock();

private void method() throws InterruptedException
{
//Trying to enter the critical section
_lock.lock(); // will wait until this thread gets the lock
try
{
// critical section
}
finally
{
//releasing the lock so that other threads can get notifies
_lock.unlock();
}
}

Using optional “fairness” parameter with ReentrantLock
ReentrantLock accepts an optional “fairness” parameter in it’s constructor. Normally what happens is, whenever a thread releases the lock anyone of the waiting threads will get the chance to acquire that lock. But there is no predefined order or priority in the selection of the thread (at least from a programmers perspective).

But if we are specifying the fairness parameter as “true” while creating a new ReentrantLock object, it gives us the guaranty that the longest waiting thread will get the lock next. Sounds pretty nice right?

Use of “Condition” in ReentrantLock
Condition can be considered as a separation of monitor methods (wait(), notify() & notifyAll()). For each ReentrantLock we can define a set of conditions and based on that we can make the threads waiting & things like that.

import java.util.concurrent.locks.Condition;
final Condition _aCondition = _lock.newCondition();
private void method1() throws InterruptedException
{
_lock.lock();
try
{
while (condition 1)
{
// Waiting for the condition to be satisfied
// Note: At this time, the thread will give up the lock
// until the condition is satisfied. (Signaled by other threads)
_aCondition.await();
}
// method body
}
finally
{
_lock.unlock();
}

}

private void method2() throws InterruptedException
{
_lock.lock();
try
{
doSomething();
if (condition 2)
{
// Signaling other threads that the condition is satisfied
// Wakes up any one of the waiting threads
_aCondition.signal();

// Wakes up all threads waiting for this condition
_aCondition.signalAll();
}

// method body
}
finally
{
_lock.unlock();
}
}


Example


We will take the same example of counter again. We have already seen how to implement counter using synchronized keyword here. Here we will see how to implement using Reentrant locks:
Counter.java

package com.vaani.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
private int count;
private Lock lock = new ReentrantLock();

public int getNextValue() {
try {
lock.lock();
count++;
}
finally {
lock.unlock();
return count;
}

}


}

Worker.java
Now worker threads starts on the counter object and start incrementing the value:

package com.vaani.lock;

public class Worker implements Runnable {
private Counter counter;
private boolean increment;
private int count;

public Worker(Counter counter, boolean increment, int count) {
this.counter = counter;
this.increment = increment;
this.count = count;
}

public void run() {
for (int i = 0; i < this.count; i++) {
System.out.println(this.counter.getNextValue());


}
}
}

Now let's put worker's on work – as in demo:

package com.vaani.lock.demo;
import com.vaani.lock.*;
public class ReentrantLockDemo {
public static void main(String[] args) throws Exception {
Counter counter = new Counter();
Thread t1 = new Thread(new Worker(counter, true, 10000));
t1.start();
Thread t2 = new Thread(new Worker(counter, false, 10000));
t2.start();

t1.join();
t2.join();
System.out.println("Final count: " + counter.getNextValue());
}
}


Download the source



Source code above can be downloaded from here.


No comments:

Post a Comment