Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java 101: The next generation: Java concurrency without the pain, Part 2

Locking, atomic variables, Fork/Join, and what to expect in Java 8

  • Print
  • Feedback

Page 2 of 7

The Locking framework, which is found in java.util.concurrent.locks, addresses these limitations.

Locks

The Lock interface provides more extensive locking operations than it's possible to obtain via synchronized methods and statements. For instance, you could use Lock to immediately back out of a lock-acquisition attempt if a lock wasn't available, or you could wait indefinitely to acquire a lock and back out only if interrupted.

Lock declares the following methods:

  • void lock() acquires a lock, disabling the current thread when the lock is not available. The thread remains dormant until the lock becomes available.
  • void lockInterruptibly() is similar to void lock() but allows the disabled thread to be interrupted and resume execution through a thrown java.lang.InterruptedException. (Note that interrupting lock acquisition is not supported in all cases.)
  • Condition newCondition() returns a new Condition instance that's bound to a given Lock instance. If the Lock implementation doesn't support conditions, java.lang.UnsupportedOperationException is thrown. (I discuss conditions later in this article.)
  • void lock() acquires a lock, disabling the current thread when the lock is not available. The thread remains dormant until the lock becomes available.
  • boolean tryLock() acquires a lock only when it's free at the time of invocation. This method returns true when the lock is acquired; otherwise, it returns false.
  • boolean tryLock(long time, TimeUnit unit) is similar to boolean tryLock(); however, it lets you specify an amount of time to wait for the lock to become available. Pass the magnitude of the delay to time and the units represented by this delay to unit. For example, you might pass 2 to time and TimeUnit.SECONDS to unit. (The java.util.concurrent.TimeUnit enum also offers DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, and NANOSECONDS.) This method throws InterruptedException when the current thread is interrupted while acquiring the lock (in cases where interrupting lock acquisition is supported).
  • void unlock() releases the lock.

It's important to always release a held lock. The Javadoc for the Lock interface presents the following idiom for locking a lock and ensuring that the lock is always unlocked:

Lock l = ...; // ... is a placeholder for code that obtains the lock
l.lock();
try 
{
  // access the resource protected by this lock
}
catch(Exception ex) 
{
  // restore invariants
}
finally 
{
   l.unlock();
}

Lock's Javadoc also discusses the memory synchronization semantics that are expected of Lock implementations. Essentially, Lock implementations must behave as the built-in monitor lock, enforcing mutual exclusion and visibility.

Working with locks

The ReentrantLock class implements Lock and describes a reentrant mutual exclusion lock. The lock is associated with an acquisition count. When a thread holds the lock and re-acquires the lock, the acquisition count is incremented and the lock must be released twice.

ReentrantLock offers the same concurrency and memory semantics as the implicit monitor lock normally accessed using synchronized methods and statements. However, it has extended capabilities and offers better performance under high thread contention (that is, when threads are frequently asking to acquire a lock that is already held by another thread). When many threads attempt to access a shared resource, the JVM spends less time scheduling these threads and more time executing them.

  • Print
  • Feedback

Resources

Previous articles in Java 101: The next generation

Concurrency tutorials on JavaWorld:

  • Modern threading for not-quite-beginners (Cameron Laird, JavaWorld, January 2013): Get an overview of callable and runnable, learn more about synchronized blocks, and find out how you can use java.util.concurrent to work around deadlock and similar threading pitfalls.
  • Multicore processing for client-side Java applications (Kirill Grouchnikov, JavaWorld, September 2007): Get a hands-on introduction to collection sorting using the CountDownLatch and Executors.newFixedThreadPool concurrency utilities.
  • Java concurrency with thread gates (Obi Ezechukwu, JavaWorld, March 2009): See the Java concurrency utilities at work in a realistic implementation of the Thread Gates concurrency pattern.
  • Hyper-threaded Java (Randall Scarberry, JavaWorld, November 2006): See for yourself how two java.util.concurrent classes were used to optimize thread use for faster performance in a real-world application.
  • Java Tip 144: When to use ForkJoinPool vs ExecutorService (Madalin Ilie, JavaWorld, October 2011): Demonstrates the performance impact of replacing the standard ExecutorService class with ForkJoinPool in a web crawler.