Java Concurrency Tutorial – Reentrant Locks

Java’s synchronized keyword is a wonderful tool – it allows us a simple and reliable way to synchronize access to critical sections and it’s not too hard to understand.

But sometimes we need more control over synchronization. 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.

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

Now, let’s see some examples.

An Read/Write Lock Example

So how does one use a lock? It’s pretty simple: just acquire and release (and never forget to release – finally is your friend!).

Imagine we have a very simple case where we need to synchronize access to a pair of variables. One is a simple value and another is derived based on some lengthy calculation. First, this is how we would perform that with the synchronized keyword.

public class Calculator {
    private int calculatedValue;
    private int value;

    public synchronized void calculate(int value) {
        this.value = value;
        this.calculatedValue = doMySlowCalculation(value);
    }

    public synchronized int getCalculatedValue() {
        return calculatedValue;
    }

    public synchronized int getValue() {
        return value;
    }
}

Simple, but if we have a lot of contention or if we perform a lot of reads and few writes, synchronization could hurt performance. Since frequently reads occur a lot more often than writes, Using a ReadWriteLock helps us minimize the issue:

public class Calculator {
    private int calculatedValue;
    private int value;
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void calculate(int value) {
        lock.writeLock().lock();
        try {
            this.value = value;
            this.calculatedValue = doMySlowCalculation(value);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getCalculatedValue() {
        lock.readLock().lock();
        try {
            return calculatedValue;
        } finally {
            lock.readLock().unlock();
        }
    }

    public int getValue() {
        lock.readLock().lock();
        try {
            return value;
        } finally {
            lock.readLock().unlock();
        }
    }
}

This example actually shows one big advantage using synchronized has: it is concise and more foolproof than using explicit locks. But locks give use flexibility we wouldn’t otherwise have.

In the example above, we can have hundreds of threads reading the same value at once with no issue, and we only block readers when we acquire the write lock. Remember that: many readers can acquire the read lock at the same time, but there are no readers OR writers allowed when acquiring the write lock.

A More Typical Use

Our first example may leave you confused or not totally convinced that explicit locks are useful. Aren’t there other uses for them that aren’t so contrived? Certainly!

We at Carfey have used explicit locks to solve many problems. One example is when you have various tasks which can run concurrently, but you don’t want more than one of the same type running at the same time. One clean way to implement it is with locks. It could be done with synchronized, but locks give us the ability to fail after timing out.

As a bonus, you’ll note we used a mix of synchronized and explicit locks – sometimes one is just cleaner and simpler than the other.

public class TaskRunner {
    private Map<Class<? extends Runnable>,  Lock> mLocks =
            new HashMap<Class<? extends Runnable>,  Lock>();

    public void runTaskUniquely(Runnable r, int secondsToWait) {
        Lock lock = getLock(r.getClass());
        boolean acquired = lock.tryLock(secondsToWait, TimeUnit.SECONDS);
        if (acquired) {
            try {
                r.run();
            } finally {
                lock.unlock();
            }
        } else {
            // failure code here
        }
    }

    private synchronized Lock getLock(Class clazz) {
        Lock l = mLocks.get(clazz);
        if (l == null) {
            l = new ReentrantLock();
            mLocks.put(clazz, l);
        }
        return l;
    }
}

These two examples should give you a pretty good idea of how to use both plan Locks and ReadWriteLocks. As with synchronized, don’t worry about reacquiring the same lock – there will be no issue in the locks provided in the JDK since they are reentrant.

Whenever you’re dealing with concurrency, there are dangers. Always remember the following:

  • Release all locks in finally block. This is rule 1 for a reason.
  • Beware of thread starvation! The fair setting in ReentrantLocks may be useful if you have many readers and occasional writers that you don’t want waiting forever. It’s possible a writer could wait a very long time (maybe forever) if there are constantly read locks held by other threads.
  • Use synchronized where possible. You will avoid bugs and keep your code cleaner.
  • Use tryLock() if you don’t want a thread waiting indefinitely to acquire a lock – this is similar to wait lock timeouts that databases have.

That’s about it! If you have questions or comments, feel free to leave them below.

Reference: Java Concurrency Part 2 – Reentrant Locks from our JCG partners at the Carfey Software blog.

Related Articles :
Related Whitepaper:

Bulletproof Java Code: A Practical Strategy for Developing Functional, Reliable, and Secure Java Code

Use Java? If you do, you know that Java software can be used to drive application logic of Web services or Web applications. Perhaps you use it for desktop applications? Or, embedded devices? Whatever your use of Java code, functional errors are the enemy!

To combat this enemy, your team might already perform functional testing. Even so, you're taking significant risks if you have not yet implemented a comprehensive team-wide quality management strategy. Such a strategy alleviates reliability, security, and performance problems to ensure that your code is free of functionality errors.Read this article to learn about this simple four-step strategy that is proven to make Java code more reliable, more secure, and easier to maintain.

Get it Now!  

2 Responses to "Java Concurrency Tutorial – Reentrant Locks"

  1. Vibhav Gupta says:

    Is what we are doing in this is that we declare an object(lock) of class Lock in method runTaskUniquely which calls the method getLock. In class getLock, we aquire a ReentrentLock on the argument passed to the method and than in method runTaskUniquely, we check whether lock is acquired or not, that is, if it is aquired, than we run the Runnable class, else we release the lock.

Leave a Reply


four − 1 =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books