std::unique_lock<> for Java

Abdulla Abdurakhmanov
2 min readFeb 27, 2020

--

Let me be clear from the beginning:

Concurrent locks generally are naughty and bad, and you should avoid them at all costs. However, sometimes you just can’t. In my experience, it is mostly for the cases when you have to implement a third-party API implementation that supposed to be reentrant, concurrent and has to manage some state and lock access to it efficiently.

Motivation

There two common ways to use concurrency in Java:

  • A high-level synchronized keyword
  • Java Concurrent API (java.util.concurrent.*)

For many cases the synchronized would be good enough, but sometimes you just need more control on locks to avoid deadlocks, spend less time inside a lock, etc.

So, let’s say we’d like to:

  • do some work in a locked/reentrant area;
  • based on the result of this work, do some other work outside lock.

With the standard Java Concurrent API it would be something like:

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
class Test {
private final Lock myLock = new ReentrantLock();
private AnyYourStatefulObject myState;

private void dangerousWork() throws IOException {
// do something with the state
// which also might cause an IO exception for example
myState.load...
}

private void doSomethingOutside(Object resOfTheDangerousWork){
// doesn't matter what we do here
// but we would like to do it without any lock
}

void myReentrantFunction() {

myLock.lock();
boolean unlocked = false;
try {
// do something inside a restricted area
dangerousWork();
// we can't release lock yet,
// because the state might be changed from another thread
if(myState.hasDoneSomethingWeExpect()) {
// we have to save a local copy of the result
Object someStateResult = myState.getSomeStateResult();
myLock.unlock();
unlocked = true;
// Doing something outside lock
doSomethingOutside(someStateResult);

}
else {
// do something without unlocking
myState.clearIt();
}
}
finally {
if(!unlocked)
myLock.unlock();
}

}
}

Look at the myReentrantFunction the method here, and it is a bloody mess.

Solving

To solve the cases like this, C++ provides the std::unique_lock<>, but sadly we don’t have anything similar in Java.

Fortunately, though, we have AutoClosable and try-with-resources to do it much better:

Despite the fact that Josh Bloch doesn’t like the idea:

This construct was designed for one thing and one thing only: resource management. It was not designed for locking.

I’m going to implement this safer and less messy with the help of try-with-resources and the following auxiliary class:

Now we can do this instead for the example above:

void myReentrantFunction() {

try(final var monitor = UniqueLockMonitor.lockAndMonitor(myLock)) {
dangerousWork();
if (myState.hasDoneSomethingWeExpect()) {
Object someStateResult = myState.getSomeStateResult();
monitor.unlock();
doSomethingOutside(someStateResult);
} else {
myState.clearIt();
}
}
}

Locking management via try-with-resource might be a controversial topic in Java, but in fact, for the cases like above, they really help to code safer, avoid lock mistakes, deadlocks and boilerplate.

--

--

Abdulla Abdurakhmanov
Abdulla Abdurakhmanov

No responses yet