|
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 ofThe 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.
Thanks for reading this newsletter on our website. We also have a mailing list. That is where the real action takes place (webinars, free reports, etc.). Maybe subscribe today?
Advanced Java Courses on Crete:Java Specialists Master Course 18-21 June 2013 and
Concurrency Specialists Course 6-9 August 2013.
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-resource 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
Update: A more complete set of tests is available in
AutoLockTest.
Language Articles
Related Java Course
Discuss at The Java Specialist Club
|