简体   繁体   中英

ReentrantLock with CompletableFuture

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:

  • Acquires the lock if it is not held by another thread and returns immediately, setting the lock hold count to one
  • If the current thread already holds the lock then the hold count is incremented by one and the method returns immediately
  • If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired, at which time the lock hold count is set to one.

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!

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()
     }
   }
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM