【问题标题】:ReentrantLock with CompletableFuture带有 CompletableFuture 的 ReentrantLock
【发布时间】:2020-01-14 11:24:58
【问题描述】:

在 CompletableFuture 世界之前,如果我想锁定一个变量,我可以这样做:

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

(显然,这是一个非常简化的示例,我也有其他方法尝试对 myLockableList 进行操作)。

根据 ReentrantLock,3 个场景中的 1 个会发生:

  • 如果没有被其他线程持有,则获取锁并立即返回,将锁持有计数设置为 1
  • 如果当前线程已经持有锁,那么持有计数加一并且方法立即返回
  • 如果锁由另一个线程持有,则当前线程将出于线程调度目的而被禁用并处于休眠状态,直到获得锁为止,此时锁持有计数设置为 1。

这一切都很好,并且正是我期望它的行为:如果在同一个线程中工作,我应该知道我是否锁定了资源,如果另一个线程锁定了资源,我想等到它变得可用.

输入 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();
        }
      }
    });
  }

我希望 CompletableFuture 的行为是相同的。但是,A CompletableFuture 可能会使用新线程执行上述操作,或者(如果我的理解是正确的)它可能会使用已经在使用的线程。如果第二个发生(被重用的线程已经获得了锁),那么我的锁不会暂停线程并等待锁,相反,它实际上可能立即返回(看起来它已经获得了锁)!

我似乎无法在文档中找到明确的答案。我是否正确理解了情况?如果是这样,我们在使用 CompletableFuture 时应该如何锁定资源?

非常感谢您的建议!

【问题讨论】:

  • “它可能使用了一个已经在使用的线程”。不,它可能不会,因为线程正在使用中。它可能会使用一个空闲线程,它会像往常一样尝试获取锁。你的finally 子句是错误的,你不应该检查isLocked()lock()try 的开头,而 unlock()finally 子句中。没有如果。
  • 好的,谢谢。所以我的理解是不正确的:)所以我对锁定没有问题。
  • 好吧,你可以(使用isLocked()),所以你可能想通过这个docs.oracle.com/javase/tutorial/essential/concurrency/…
  • 我会看看 - 谢谢,W.R.T isLocked(),我在 lock() 之前有一个包含一些东西的方法,根据结果,这可能意味着 lock() 未被调用。发生这种情况时,调用 unlock() 会抛出 Illegal 异常。我本可以使用嵌套的 try-finally 块,但这变得很乱
  • isLocked() 的通常(尽管非常罕见)用例是tryLock()。更好的是使用更高级别的并发原语,这样您就不需要处理原始锁。我推荐阅读并发教程。

标签: java multithreading locking completable-future reentrantlock


【解决方案1】:

除了 cmets 中推荐的 concurrency tutorial 之外,Javadoc for ReentrantLock 有很多值得一读的信息。

这个,例如:

ReentrantLock 归上次成功锁定但尚未解锁的线程所有。

您的示例使用isLocked(),Javadoc 说您不应该这样做:

此方法设计用于监控系统状态,而不是用于同步控制。

最后,Javadoc 包含一个很好的用法示例,在“try”块中显示lock(),然后在“finally”块中显示unlock()

建议的做法是始终在调用 lock 之后立即使用 try 块,最常见的是在构造之前/之后,例如:

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

【讨论】:

  • 我同意你所说的一切 - 我没有先查找就将 isLocked() 方法放入其中。我真正的误解来自认为 CompletableFutures 重用了暂停的线程。
猜你喜欢
  • 1970-01-01
  • 2015-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多