Tom McAdam

new java 8 map methods

The new methods on the Map interface in Java 8 really are lovely. I recently found a need to use them, rather than just read about them.

I was adapting a GroupLock class we have, which locks a set of things (we’ll call them integers for the sake of this post to avoid generics everywhere). It had been a rather simple implementation containing the currently-locked things in a Set<Integer>, but we wanted to improve it to include properties such as re-entrant locking.

So our implementation now has a ConcurrentMap<Integer, ReentrantLock> to control the locking.

adding a lock

Before Java 8 life was hard if we wanted to associate a key with a value in a map in an atomic fashion. To ensure we didn’t add to the map if the key already existed we’d have needed to do something along the lines of:

Map
   
     map = Maps.newConcurrentMap();
...
ReentrantLock lock = new ReentrantLock();
map.putIfAbsent(k, lock);
   

This is quite clunky and, not that we should be overly worried about it, allocates a new object which may well be thrown away frequently if keys are usually already mapped.

In Java 8 we have the lovely method computeIfAbsent which will compute the value and add to the map. So all we need to do is:

map.computeIfAbsent(k, (v) -> new Value())

Much cleaner. This was the obvious bit. But where Java 8 features came into their own was in the clean-up code.

unlocking

Unlocking a key comes in two parts: firstly, we need to perform the unlock. That’s pretty straightforward:

private void unlock(Integer key) {
  ReentrantLock lock = map.get(key)
  if (lock == null) {
    throw new IllegalStateException();
  } 
  lock.unlock();
}

Secondly, though, we need to tidy up our Map to avoid it becoming a massive memory leak over time. When contemplating this, I started coming out in cold sweats thinking about hideous synchronisation needed on all uses of the Map, and some code like:

synchronized(map) {
  ReentrantLock lock = map.get(id);
  if (lock != null && !lock.isLocked()) {
    map.remove(key);
  }
} 

I then took another look at the documentation in the hope of a Java 8 method to help me out. computeIfPresent did just that. As the name suggests, it will compute the value for a key, if that key is present and, crucially, remove the association if the computed value is null. So, with the help of this new method we’re able to do the much cleaner:

  map.computeIfPresent(id, (key, value) -> value.isLocked() ? value : null );

Do be careful, though. The Map interface doesn’t make any concurrency guarantees about these new methods. You’ll need to check the implementations, such as ConcurrentMap to find out whether they’re atomic.

If you enjoyed the read, drop us a comment below or share the article, follow us on Twitter or subscribe to our #MetaBeers newsletter. Before you go, grab a PDF of the article, and let us know if it’s time we worked together.

blog comments powered by Disqus