在`ReentrantReadWriteLock`

时间:2016-12-13 17:54:00

标签: java multithreading concurrency reentrantreadwritelock

我尝试使用ReentrantReadWriteLock来控制对具有许多不同读写操作的相当复杂的数据结构的访问时出现了一些密切相关的问题。根据文档中的示例,我已经获取了读取和写入锁定,并且我使用它们(从我所知道的)成功管理对此数据结构的并发访问。但是,在调试另一个问题时,我注意到有时我在同一个线程中获得了多个读锁定。这个的基本原因是我有许多复杂的查询调用更简单的查询(参见下面的例子)。复杂查询可以被视为一个事务,即getVersion()datamyQuery的访问之间不应该有任何写入。这个当前的解决方案有效,但这意味着在代码的某些地方,我将拥有同一个线程拥有的多个读锁。我注意到写等效方法有一个isHeldByCurrentThread()方法,但在readLock中很奇怪。我无法找到以下内容:

  • 在同一个帖子中有多个读锁定是不好(风格,性能或未来错误的风险)?
  • 使用WriteLock.isHeldByCurrentThread检查错误是不好的(例如,期望写锁定但不存在,或者作为释放写锁定的条件)?
  • 如果这是一个问题,
  • 是否有最佳做法来决定如何继续?我是否将lockedAquired布尔值传递给可能已从拥有锁定的代码调用的方法,我确保它们是唯一获得的,还是应该使用ReadLock.tryLock()

以下是代码示例:

public class GraphWorldModel {

  //unfair RW Lock (default, see javadoc)
  protected final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(false);
  protected final ReentrantReadWriteLock.ReadLock readLock = rwl.readLock();
  protected final ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();

  protected long versionID = 0;
  protected Object data = null;

  @Override
  public long getVersion() {
      long result = -1;
      readLock.lock();
      try {
          result = this.versionID;
      } finally {

          readLock.unlock();
      }
      return result;
  }
  public ResultObject myQuery() {
      //do some work
      readLock.lock();
      try {
        long version = getVersion();
        ResultObject result = new ResultObject(this.data, version);
        //note: querying version and result should be atomic!
      } finally {
        readLock.unlock();
      }
      //do some more work
      return result;
  }
}

1 个答案:

答案 0 :(得分:1)

  
      
  • 在同一个帖子中有多个读锁定是不好(风格,性能或未来错误的风险)?
  •   

我会说这是有问题的风格。首先,正如@JBNizet指出的那样,你试图获得一个已经拥有的锁定没有问题。锁只是将读取器计数增加1,然后在最终解锁时减少它。

但是,它确实意味着您必须跨越内存屏障(volatile读取)以更新共享锁统计信息,这意味着性能受到影响。这取决于此代码执行的频率,以确定它是否会产生明显的性能差异。

然而,就错误而言,有一个很大的问题。如果你只是谈论只读锁,那么我不认为错误的可能性更大。实际上试图修复双锁问题(见下文)可能存在更大的风险。但是在你的代码中,try / finally逻辑确保锁被正确使用并在完成后解锁。

然而,如果您正在讨论混合读写锁定,那么您需要了解以下代码死锁:

ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
ReadLock readLock = rrwl.readLock();
WriteLock writeLock = rrwl.writeLock();
readLock.lock();
writeLock.lock();

或者至少在其他代码解锁read-lock之前它会停止。如果要在代码中混合使用读写锁定,则必须防止双重锁定。

  
      
  • 使用WriteLock.isHeldByCurrentThread检查错误是不好的(例如,期望写锁定但不存在,或作为释放写锁定的条件)?
  •   

这将为您的代码添加大量逻辑,这样做风险更大,但如果性能影响很大,则可能值得。也就是说,请参阅下面的替代方案。

  
      如果这是一个问题,
  • 是否有最佳做法来决定如何继续?我是否将lockedAquired布尔值传递给可能已经拥有锁的代码调用的方法,我确保它们是唯一获得的,还是应该使用ReadLock.tryLock()?
  •   

我要做的是创建一个名为getVersionLocked()的内核方法。类似的东西:

@Override
public long getVersion() {
    readLock.lock();
    try {
        // NOTE: I refactored this to return out of the try/finally which is a fine pattern
        return getVersionLocked();
    } finally {
        readLock.unlock();
    }
}
public ResultObject myQuery() {
    //do some work
    readLock.lock();
    ResultObject result;
    try {
      long version = getVersionLocked();
      result = new ResultObject(this.data, version);
      //note: querying version and result should be atomic!
    } finally {
      readLock.unlock();
    }
    //do some more work
    return result;
}
// NOTE: to call this method, we must already be locked
private long getVersionLocked() {
    return this.versionID;
}

然后,您可以决定调用方法的锁定或解锁版本,只执行readLock()一次。

这样做风险更大,因为当锁定时,您可以致电getVersionLocked(),但我认为这是可接受的风险。