检查 `ReentrantReadWriteLock` 中的 readLock 占有
Checking for readLock possession in `ReentrantReadWriteLock`
我在尝试使用 ReentrantReadWriteLock
来控制对具有许多不同读写操作的相当复杂的数据结构的访问时突然出现了一些密切相关的问题。根据文档中的示例,我获取了一个读锁和一个写锁,并且我正在使用它们(据我所知)成功地管理对该数据结构的并发访问。但是,在调试另一个问题时,我注意到有时我会在同一个线程中获取多个读锁。这样做的基本原因是我有许多复杂的查询调用更简单的查询(参见下面的示例)。复杂的查询可以被认为是一个事务,即在 getVersion()
和访问 myQuery
中的 data
之间不应该有写操作。当前的解决方案有效,但这意味着在代码的某些地方,同一个线程将拥有多个读锁。我注意到写等价物有一个 isHeldByCurrentThread()
方法,但奇怪的是 readLock
中没有。我找不到以下内容:
- 在同一个线程中有多个读取锁是否不好(糟糕的风格、性能或未来错误的风险)?
- 使用
WriteLock.isHeldByCurrentThread
来检查错误是不好的(例如,期望写锁但是 none 存在,或者作为释放写锁的条件)?
- 如果这是一个问题,是否有最佳做法来决定如何继续?我是否将
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;
}
}
- is it bad (poor style, performance or risk of future bugs) to have multiple read locks in the same thread?
我会说这是有问题的风格。首先,正如@JBNizet 指出的那样,您尝试再次获取您已经拥有的锁没有任何问题。锁只是将 reader 计数加一,然后在最终解锁时将其递减。
但是,这确实意味着您必须跨越内存障碍(volatile
读取)来更新共享锁统计信息,这意味着性能下降。这取决于此代码的执行频率是否会产生任何明显的性能差异。
然而,就错误而言,有一个很大的陷阱。如果您只是在谈论只读锁,那么我认为不会有更多的错误机会。实际上试图解决双锁问题(见下文)可能有更大的风险。但是在您的代码中,try/finally 逻辑确保正确使用锁并在完成后解锁。
但是,如果你在谈论混合读写锁,那么你需要了解以下代码死锁:
ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
ReadLock readLock = rrwl.readLock();
WriteLock writeLock = rrwl.writeLock();
readLock.lock();
writeLock.lock();
或者至少它会停止,直到其他代码解锁 read-lock
。如果您要在代码中混合读取和写入锁,那么您必须防止双重锁定。
- is is bad to use WriteLock.isHeldByCurrentThread to check for errors (e.g., expecting a write lock but none is present, or as a condition to release the write lock)?
这会在您的代码中添加很多逻辑,这会带来更大的风险,但如果对性能的影响很大,则可能值得。也就是说,请参阅下面的替代方案。
- is there a best practice to decide how to proceed if this is an issue? Do I pass a lockedAquired boolean to methods that may be called from code that already possesses a lock, do I ensure they are uniquely acquired, or should I use 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()
,但我认为这是可以接受的风险。
我在尝试使用 ReentrantReadWriteLock
来控制对具有许多不同读写操作的相当复杂的数据结构的访问时突然出现了一些密切相关的问题。根据文档中的示例,我获取了一个读锁和一个写锁,并且我正在使用它们(据我所知)成功地管理对该数据结构的并发访问。但是,在调试另一个问题时,我注意到有时我会在同一个线程中获取多个读锁。这样做的基本原因是我有许多复杂的查询调用更简单的查询(参见下面的示例)。复杂的查询可以被认为是一个事务,即在 getVersion()
和访问 myQuery
中的 data
之间不应该有写操作。当前的解决方案有效,但这意味着在代码的某些地方,同一个线程将拥有多个读锁。我注意到写等价物有一个 isHeldByCurrentThread()
方法,但奇怪的是 readLock
中没有。我找不到以下内容:
- 在同一个线程中有多个读取锁是否不好(糟糕的风格、性能或未来错误的风险)?
- 使用
WriteLock.isHeldByCurrentThread
来检查错误是不好的(例如,期望写锁但是 none 存在,或者作为释放写锁的条件)? - 如果这是一个问题,是否有最佳做法来决定如何继续?我是否将
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;
}
}
- is it bad (poor style, performance or risk of future bugs) to have multiple read locks in the same thread?
我会说这是有问题的风格。首先,正如@JBNizet 指出的那样,您尝试再次获取您已经拥有的锁没有任何问题。锁只是将 reader 计数加一,然后在最终解锁时将其递减。
但是,这确实意味着您必须跨越内存障碍(volatile
读取)来更新共享锁统计信息,这意味着性能下降。这取决于此代码的执行频率是否会产生任何明显的性能差异。
然而,就错误而言,有一个很大的陷阱。如果您只是在谈论只读锁,那么我认为不会有更多的错误机会。实际上试图解决双锁问题(见下文)可能有更大的风险。但是在您的代码中,try/finally 逻辑确保正确使用锁并在完成后解锁。
但是,如果你在谈论混合读写锁,那么你需要了解以下代码死锁:
ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
ReadLock readLock = rrwl.readLock();
WriteLock writeLock = rrwl.writeLock();
readLock.lock();
writeLock.lock();
或者至少它会停止,直到其他代码解锁 read-lock
。如果您要在代码中混合读取和写入锁,那么您必须防止双重锁定。
- is is bad to use WriteLock.isHeldByCurrentThread to check for errors (e.g., expecting a write lock but none is present, or as a condition to release the write lock)?
这会在您的代码中添加很多逻辑,这会带来更大的风险,但如果对性能的影响很大,则可能值得。也就是说,请参阅下面的替代方案。
- is there a best practice to decide how to proceed if this is an issue? Do I pass a lockedAquired boolean to methods that may be called from code that already possesses a lock, do I ensure they are uniquely acquired, or should I use 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()
,但我认为这是可以接受的风险。