|
The Java Specialists' Newsletter
Issue 190 2011-02-27
Category:
Language
Java version: Java 7 Automatically Unlocking with Java 7by Dr. Heinz M. KabutzAbstract: In this newsletter we explore my favourite new Java 7 feature "try-with-resource" and see how we can use this mechanism to automatically unlock Java 5 locks.

Welcome to the 190th issue of The Java(tm) Specialists' Newsletter, sent to you from the
beautiful Island of Crete. I started Greek lessons last week.
With 8 hours under my belt, I headed off to a Greek dinner
party last night. For the first time, I could understand the
conversations going on around me and respond relatively
correctly. Watch this space for some
funny stories as I learn this confusing language, where a
"t'axi" is a classroom (emphasis on "a") and a "tax'i" that
smelly vehicle you get driven to the airport in. Where a
professor is "kathiyitis" and a janitor a
"katharistis". My job description is "programmatistis
ypologiston kai kathiyitis pliroforikis" (computer programmer
and computer science instructor). Maybe I will just use
"anergos" (unemployed) in future - that is so much easier to
remember ... :-) [besides, that is how banks view
self-employed people like me anyway ;-]
Talking of Greece, we are doing our Masters course from
the 7-10 June here in Chania. Let
me know if you need some nice warm sunny
weather and great food to make 4 intense days of learning
more bearable.
Our New Java Design Patterns Self-Study Course is Now Available
Automatically Unlocking with Java 7
One of the quirks of Java is how difficult it is to correctly
close resources. For example, if you open a file, it is
easy to forget to close it. Your code might even work on
some operating systems. But on others, you will run out of
file handles.
To simplify things, Java 7 introduced the new
try-with-resources statement, which
automatically closes any AutoCloseable resources referenced
in the try statement. For example, instead of manually
closing streams ourselves, we can simply do this:
import java.io.*;
public class AutoClosingFiles {
public static void main(String[] args) throws IOException {
try (
PrintStream out = new PrintStream (
new BufferedOutputStream(
new FileOutputStream("file.txt")))
) {
out.print("This would normally not be written!");
}
}
}
Note that there is never a semicolon at the end of the
"try ()" declaration, even when you specify several resources
that must be closed individually. Update: Thanks to
Marco Hunsicker for pointing out that they are working on
allowing us to have tailing semicolons in try-with-resource
declarations. Here
is a link. Since I'm a Mac OS X user, I had to
compile the OpenJDK myself.
import java.io.*;
public class AutoClosingFiles2 {
public static void main(String[] args) throws IOException {
try (
FileOutputStream fout = new FileOutputStream("file.txt");
BufferedOutputStream bout = new BufferedOutputStream(fout);
PrintStream out = new PrintStream (bout)
) {
out.print("This would normally not be written!");
}
}
}
There are two new features in Java 7 that allow this. First
we have the java.lang.AutoCloseable interface, implemented by
a whopping 553 classes in the JDK! To put this in
perspective, java.io.Serializable is implemented 3919 times,
java.lang.Cloneable 1297 times and java.lang.Comparable 759
times. java.lang.Runnable is only implemented 186 times and
java.lang.Iterable 252 times.
The second new feature is the method
Throwable.addSuppressed(Throwable). This method has some
strange semantics. It is only ever called by the
try-with-resources code construct. Here is how it works:
- If addSuppressed is called the first time with
null, then it will never have
suppressed throwables attached.
- If it is subsequently called with a non-null element,
we will see a NullPointerException.
- If addSuppressed is called with a non-null throwable,
then it will contain a collection of throwables.
- If it is subsequently called with a null element,
we will see a NullPointerException.
These weird semantics are not meant to be understandable, but
rather support the new try-with-resource mechanism. In a
future newsletter, I will explore this in more detail. For
now it will suffice to know that the try-with-resource
guarantees that created objects will be closed again, with
exceptions managed correctly.
When I woke up yesterday, my mind wandered to Java 5 locks
and I was trying to figure out why they were not also
AutoCloseable. After all, over 500 other classes were. In
my half-sleeping state, I figured out why and also worked out
a way to make it work.
The try-with-resource works well with objects that are
constructed and then closed immediately again. It will not
work with resources that need to be kept alive. Locks would
fall into this latter category. We construct locks once and
then use them for as long as we need to protect the critical
section. Thus we cannot autoclose them when they go out of
scope. Problem is, they won't go out of scope at the same
time that we want to unlock them.
However, we can write a wrapper that automatically unlocks
the lock for us. We call lock() in the constructor and
unlock() in the close() method overridden from AutoCloseable:
import java.util.concurrent.locks.*;
public class AutoLockSimple implements AutoCloseable {
private final Lock lock;
public AutoLockSimple(Lock lock) {
this.lock = lock;
lock.lock();
}
public void close() {
lock.unlock();
}
}
Disclaimer: I have not used Java 7 in a production
environment yet. Thus I do not know if there are any issues
with my idea of AutoLockSimple. It seems good to me, but I
give no guarantees. Please let me know if you think of
anything. One of the issues that could be a problem is that
we will make new objects every time we lock. This
unnecessary object creation could end up straining the GC.
However, from my initial tests, it seems that escape analysis
takes care of the object construction cost.
Here is how we use it in our code. We use the handle to the
ReentrantLock so that we can call the isHeldByCurrentThread()
method. This way we can determine whether we are locked or
not.
import java.util.concurrent.locks.*;
public class AutoLockSimpleTest {
private final static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
try (new AutoLockSimple(lock)) {
printLockStatus();
}
printLockStatus();
}
private static void printLockStatus() {
System.out.println("We are locked: " +
lock.isHeldByCurrentThread());
}
}
Output is simply this:
We are locked: true
We are locked: false
This is nice, but we can make it a bit less obvious that we
have to construct an object by using static factory methods.
In addition, we can then also implement a lockInterruptibly()
mechanism. In this code, I define the various locking
approaches as static inner classes. The
package eu.javaspecialists.concurrent;
import java.util.concurrent.locks.*;
public class AutoLock implements AutoCloseable {
public static AutoLock lock(Lock lock) {
return new AutoLockNormal(lock);
}
public static AutoLock lockInterruptibly(Lock lock)
throws InterruptedException {
return new AutoLockInterruptibly(lock);
}
private final Lock lock;
public void close() {
lock.unlock();
}
private AutoLock(Lock lock) {
this.lock = lock;
}
private static class AutoLockNormal extends AutoLock {
public AutoLockNormal(Lock lock) {
super(lock);
lock.lock();
}
}
private static class AutoLockInterruptibly extends AutoLock {
public AutoLockInterruptibly(Lock lock)
throws InterruptedException {
super(lock);
lock.lockInterruptibly();
}
}
}
This in combination with static imports is far more readable
than the try-finally approach commonly used for Java 5 locks.
Here is how you would call the factory methods:
try (lock(lock)) {
printLockStatus();
}
And if you want to be interruptible, then we do it like this:
try (lockInterruptibly(lock)) {
printLockStatus();
}
It will be challenging to implement tryLock(), because we
only want to unlock() if we were successful in our tryLock().
We also only want to enter the critical section if we were
able to lock.
Here is a complete test class that demonstrates how the
lock() method can be used in combination with static imports:
import java.util.concurrent.locks.*;
import static eu.javaspecialists.concurrent.AutoLock.lock;
public class AutoLockTest {
private final static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
// The old way - far more verbose
lock.lock();
try {
printLockStatus();
} finally {
lock.unlock();
}
// Heinz's new way
try (lock(lock)) {
printLockStatus();
}
printLockStatus();
}
private static void printLockStatus() {
System.out.println("We are locked: " +
lock.isHeldByCurrentThread());
}
}
Output from our code is this:
We are locked: true
We are locked: true
We are locked: false
The test code for the interruptible lock is a bit more
involved, since we need to interrupt the testing thread.
import java.util.concurrent.locks.*;
import static eu.javaspecialists.concurrent.AutoLock.*;
public class AutoLockInterruptiblyTest {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args)
throws InterruptedException {
testLock();
Thread.currentThread().interrupt();
try {
testLock();
} catch (InterruptedException ex) {
System.out.println(ex);
}
}
public static void testLock() throws InterruptedException {
try (lockInterruptibly(lock)) {
printLockStatus();
}
printLockStatus();
}
private static void printLockStatus() {
System.out.println("We are locked: " +
lock.isHeldByCurrentThread());
}
}
Here is the output from the test program:
We are locked: true
We are locked: false
java.lang.InterruptedException
Hopefully Java 7 will give me lots of new material for
newsletters. It was becoming difficult to find new things
in Java 6. In another newsletter I will show how my
use of try-with-resources is actually more correct than the
traditional try-finally. But that will have to wait for
another day.
Heinz
Language Articles
Related Java Course
Discuss at The Java Specialist Club
|