Running on Java 24-ea+21-2447 (Preview)
Home of The JavaSpecialists' Newsletter

190Automatically Unlocking with Java 7

Author: Dr. Heinz M. KabutzDate: 2011-02-27Java Version: 7Category: Language
 

Abstract: 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 ;-]

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

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:

  1. If addSuppressed is called the first time with null , then it will never have suppressed throwables attached.
  2. If it is subsequently called with a non-null element, we will see a NullPointerException.
  3. If addSuppressed is called with a non-null throwable, then it will contain a collection of throwables.
  4. 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 static final 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 static final 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.

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...