Reputation: 299
Before the world of CompletableFuture, if I wanted to lock a variable, I could do this:
private ReentrantLock myLock = new ReentrantLock();
private List<UUID> myLockableList = new ArrayList<>();
public void doStuff()
{
try
{
myLock.lock();
myLockableList.clear();
}
finally
{
if (myLock.isLocked())
{
myLock.unlock();
}
}
}
(Obviously, this is a very simplified example, I have other methods trying to operate on myLockableList, too).
According to ReentrantLock, 1 of 3 scenarios would occur:
That's all great and exactly how I expect it to behave: if working in the same thread, I should know whether I have locked the resource, and if another thread has locked the resource, I would like to wait until it becomes available.
Enter CompletableFutures...
private ReentrantLock myLock = new ReentrantLock();
private List<UUID> myLockableList = new ArrayList<>();
public CompletionStage<Void> doStuff()
{
return CompletableFuture.runAsync(() -> {
try
{
myLock.lock();
myLockableList.clear();
}
finally
{
if (myLock.isLocked())
{
myLock.unlock();
}
}
});
}
I would hope the behaviour for CompletableFuture would be the same. However, A CompletableFuture might execute the above with a new thread, or (if my understanding is correct) it might use a thread that is already in use. If the second happens (the thread being reused has already acquired the lock), then my lock would not pause the thread and wait for the lock, instead, it might actually return immediately (looking like it's acquired the lock)!
I can't seem to find a definitive answer in the documentation. Have I understood the situation correctly? If so, how should we be locking resources when using CompletableFuture?
Many thanks for any advice!
Upvotes: 0
Views: 993
Reputation: 5754
In addition to the concurrency tutorial recommended in the comments, the Javadoc for ReentrantLock has great info that's worth reading.
This, for example:
A ReentrantLock is owned by the thread last successfully locking, but not yet unlocking it.
Your example uses isLocked(), which Javadoc says you shouldn't do:
This method is designed for use in monitoring of the system state, not for synchronization control.
Finally, the Javadoc includes a great usage example showing lock()
in a "try" block followed by unlock()
in the "finally" block.
It is recommended practice to always immediately follow a call to lock with a try block, most typically in a before/after construction such as:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
Upvotes: 1