检查 ReentrantReadWriteLock 的状态(锁定 read/write,线程等待)
Checking the state of a ReentrantReadWriteLock (locked for read/write, threads waiting)
我正在实施 DocumentProvider
,它必须支持并发访问它提供的 Document
。它的大致工作原理如下:
- 文档按需加载。
- 当文档被使用时,它被保存在
activeDocuments
中(一个 Map<String, Document>
由文档 ID 索引)。
- 文档停止使用后,它会从
activeDocuments
移动到 idleDocuments
(也是按文档 ID 索引的 Map<String, Document>
)。 idleDocuments
是一个 LRU 缓存,这意味着在某些时候这个缓存中的 Document
可能会被丢弃。
- 每个文档都可以根据通常的 readers/writers 策略进行读取或写入访问。这是使用
ReentrantReadWriteLock
每个活动 Document
. 实现的
- 锁定文档的尝试是非锁定的:线程将调用
tryLock
而不是 lock
。如果它无法锁定文档,则会抛出 CannotLockException
并且线程将不得不处理它——具体如何处理超出了这个问题的范围。 (示例:显示消息 "The document is currently locked. Would you like to open a read-only copy?")
代码如下(稍微简化):
public enum LockMode {
READ,
WRITE;
}
public class DocumentManager {
/**
* Documents currently in use (indexed by documentId).<br>
* Always accessed in a synchronized(activeDocuments) block.
*/
private Map<String, Document> activeDocuments;
/**
* Documents that have been loaded but are not in use (indexed by
* documentId). This is a LRU cache and documents may be discarded if the
* cache is full.<br>
* Always accessed in a synchronized(activeDocuments) block.
*/
private Map<String, Document> idleDocuments;
/**
* Read/write locks in for the {@link #activeDocuments} (indexed by
* documentId).<br>
* Always accessed in a synchronized(readWriteLocks) block.
*/
private Map<String, ReentrantReadWriteLock> readWriteLocks;
public Document openDocument(String documentId, LockMode lockMode) throws CannotLockException {
Document document = null;
synchronized (activeDocuments) {
document = activeDocuments.get(documentId);
if (document == null) {
document = loadDocument(documentId);
activeDocuments.put(documentId, document);
}
}
if (document != null) {
tryLock(documentId, lockMode);
}
return document;
}
protected Document loadDocument(String documentId) {
synchronized (activeDocuments) {
Document document = idleDocuments.remove(documentId);
if (document == null) {
document = // load the document from disk
}
return document;
}
}
public void tryLock(String documentId, LockMode lockMode) throws CannotLockException {
synchronized (readWriteLocks) {
ReentrantReadWriteLock readWriteLock = readWriteLocks.get(documentId);
if (readWriteLock == null) {
readWriteLock = new ReentrantReadWriteLock();
readWriteLocks.put(documentId, readWriteLock);
}
Lock lock = getLock(readWriteLock, lockMode);
if (!lock.tryLock()) {
throw new CannotLockException("Cannot lock document " + documentId + " for " + lockMode);
}
}
}
public void unlock(String documentId) throws CannotUnlockException {
ReentrantReadWriteLock readWriteLock = null;
synchronized (readWriteLocks) {
readWriteLock = readWriteLocks.get(documentId);
if (readWriteLock == null) {
throw new CannotUnlockException("Cannot unlock document " + documentId
+ ": it is not currently locked");
}
Lock lock = null;
// (1) From isWriteLocked's javadoc:
// Queries if the write lock is held by any thread. This method is
// designed for use in monitoring system state, not for
// synchronization control.
if (readWriteLock.isWriteLocked()) {
lock = readWriteLock.writeLock();
} else {
lock = readWriteLock.readLock();
}
try {
lock.unlock();
} catch (IllegalMonitorStateException e) {
throw new CannotUnlockException("Cannot unlock document " + documentId
+ ": this thread does not own any lock on it", e);
}
// (2) From hasQueuedThreads's javadoc:
// Queries whether any threads are waiting to acquire the read or
// write lock. Note that because cancellations may occur at any
// time, a true return does not guarantee that any other thread will
// ever acquire a lock. This method is designed primarily for use in
// monitoring of the system state.
if (!readWriteLock.hasQueuedThreads() &&
// (3) From getReadLockCount's javadoc:
// Queries the number of read locks held for this lock. This
// method is designed for use in monitoring system state, not
// for synchronization control.
readWriteLock.getReadLockCount() == 0) {
synchronized (activeDocuments) {
Document document = activeDocuments.remove(documentId);
idleDocuments.put(documentId, document);
// Remove the lock from the map to free some space.
readWriteLocks.remove(documentId);
}
}
}
}
protected Lock getLock(ReentrantReadWriteLock lock, LockMode lockMode) {
switch (lockMode) {
case READ:
return lock.readLock();
case WRITE:
return lock.readLock();
default:
throw new IllegalArgumentException("Unknown " + LockMode.class.getName() + ": " + lockMode);
}
}
}
您可能已经在我的代码注释中看到,在方法 unlock
(标记为 (1)、(2)、(3))中有几个点我调用了 Javadoc 指示的方法它们旨在用于监视,而不是用于同步。显然这不行,所以我正在寻找替代方案。有问题的方法检查给定的 ReentrantReadWriteLock
当前是否被锁定以供读取 ((3), getReadLockCount
) 或写入 ((1), isWriteLocked
) 以及是否有线程在排队lock ((2), hasQueuedThreads
——诚然与我当前的 tryLock
场景无关,但我仍想检查一下)。基本上,我想知道有问题的文档是否刚刚从 active 变为 idle (没有人想再使用它了)并且我因此可以将其从 activeDocuments
移动到 idleDocuments
.
我可以在 DocumentProvider
中保持状态(锁定 reading/writing,线程等待),但如果锁具有(或有权访问)必要的信息,我宁愿避免这种情况.我在看上面提到的方法(isWriteLocked
,
getReadLockCount
, hasQueuedThreads
) 在我看来,在我的特定场景中,这些方法应该反映锁的实际状态,因为任何可能修改锁状态的操作总是在synchronized (readWriteLocks) {}
块,因此当我调用其中一种可能不安全的方法时,锁的状态不应改变。
你同意吗?
如果不是,我是否应该将此信息保存在 DocumentProvider
中的锁外部?
顺便说一句,如果有人发现代码有任何同步问题(例如,可能出现死锁),我将不胜感激。
您可以手动实现 ReentrantReadWriteLock
锁,而不是使用系统锁。 (请注意,java 锁期望从之前锁定它的 同一线程 执行解锁。)因为您不使用阻塞锁,所以实现很简单:
class MyReentrantReadWriteLock {
private int state; // 0 - free, -1 - write locked, n>0 - read locked n times
public bool tryLock(LockMode lockMode) {
switch(lockMode) {
case READ:
if(state == -1) return false; // Already write-locked.
state++;
return true;
case WRITE:
if(state) return false; // Already locked.
state = -1;
return true;
}
public void unlock() {
switch(state) {
case 0:
// error: not locked
case -1:
state = 0; // write unlock
break;
default:
state--; // read unlock
}
}
public bool isLocked() {
return state != 0;
}
};
因为您只在 synchronized (readWriteLocks)
下使用锁,所以您的锁实现无需担心同步问题。
UPDATE:如果想用文档锁的更复杂的东西,比如条件变量,最好区分locking ,授予读取或写入访问权限,使用计数,防止文档在使用时被卸载。
使用使用计数器,您需要的 ReentrantReadWriteLock
唯一监控方法是 .isWriteLocked
以确定解锁期间的锁类型。虽然最好从其他地方扣除 属性。
更好的设计将是:
- 将
lockMode
存储在 Document
对象中。例如,作为 bool m_isWritable;
- 用任何引用计数 class 替换
readWriteLocks
字段定义中的 ReentrantReadWriteLock
(并且可能重命名字段本身)。
这允许 DocumentManager
成为真正的工厂对象,它禁止(例如,抛出异常)打开文档,如果它已经作为可写打开(反之亦然)。
另一方面,使用文档的线程可以通过reference将它传递给任何辅助函数,这个函数可以检查文档是否可写:
// User of the document
{
doc = documentManager.openDocument("doc1", WRITE);
//...
doSomethingWithDocument(doc);
//...
documentManager.closeDocument(doc);
}
// Implementation of auxiliary function
void doSomethingWithDocument(Document doc) {
if(!doc.isWritable()) throw new Exception("Read-only document!");
// Process document
}
通过该实现,您不需要重入锁定(所有对 openDocument()
的调用都被视为对 new 文档引用的请求)。至于锁降级,可以用DocumentManager
中的简单方法实现:
void makeReadOnly(Document doc)
{
synchronized(readWriteLocks)
{
doc->m_isWritable = false;
}
}
我正在实施 DocumentProvider
,它必须支持并发访问它提供的 Document
。它的大致工作原理如下:
- 文档按需加载。
- 当文档被使用时,它被保存在
activeDocuments
中(一个Map<String, Document>
由文档 ID 索引)。 - 文档停止使用后,它会从
activeDocuments
移动到idleDocuments
(也是按文档 ID 索引的Map<String, Document>
)。idleDocuments
是一个 LRU 缓存,这意味着在某些时候这个缓存中的Document
可能会被丢弃。 - 每个文档都可以根据通常的 readers/writers 策略进行读取或写入访问。这是使用
ReentrantReadWriteLock
每个活动Document
. 实现的
- 锁定文档的尝试是非锁定的:线程将调用
tryLock
而不是lock
。如果它无法锁定文档,则会抛出CannotLockException
并且线程将不得不处理它——具体如何处理超出了这个问题的范围。 (示例:显示消息 "The document is currently locked. Would you like to open a read-only copy?")
代码如下(稍微简化):
public enum LockMode {
READ,
WRITE;
}
public class DocumentManager {
/**
* Documents currently in use (indexed by documentId).<br>
* Always accessed in a synchronized(activeDocuments) block.
*/
private Map<String, Document> activeDocuments;
/**
* Documents that have been loaded but are not in use (indexed by
* documentId). This is a LRU cache and documents may be discarded if the
* cache is full.<br>
* Always accessed in a synchronized(activeDocuments) block.
*/
private Map<String, Document> idleDocuments;
/**
* Read/write locks in for the {@link #activeDocuments} (indexed by
* documentId).<br>
* Always accessed in a synchronized(readWriteLocks) block.
*/
private Map<String, ReentrantReadWriteLock> readWriteLocks;
public Document openDocument(String documentId, LockMode lockMode) throws CannotLockException {
Document document = null;
synchronized (activeDocuments) {
document = activeDocuments.get(documentId);
if (document == null) {
document = loadDocument(documentId);
activeDocuments.put(documentId, document);
}
}
if (document != null) {
tryLock(documentId, lockMode);
}
return document;
}
protected Document loadDocument(String documentId) {
synchronized (activeDocuments) {
Document document = idleDocuments.remove(documentId);
if (document == null) {
document = // load the document from disk
}
return document;
}
}
public void tryLock(String documentId, LockMode lockMode) throws CannotLockException {
synchronized (readWriteLocks) {
ReentrantReadWriteLock readWriteLock = readWriteLocks.get(documentId);
if (readWriteLock == null) {
readWriteLock = new ReentrantReadWriteLock();
readWriteLocks.put(documentId, readWriteLock);
}
Lock lock = getLock(readWriteLock, lockMode);
if (!lock.tryLock()) {
throw new CannotLockException("Cannot lock document " + documentId + " for " + lockMode);
}
}
}
public void unlock(String documentId) throws CannotUnlockException {
ReentrantReadWriteLock readWriteLock = null;
synchronized (readWriteLocks) {
readWriteLock = readWriteLocks.get(documentId);
if (readWriteLock == null) {
throw new CannotUnlockException("Cannot unlock document " + documentId
+ ": it is not currently locked");
}
Lock lock = null;
// (1) From isWriteLocked's javadoc:
// Queries if the write lock is held by any thread. This method is
// designed for use in monitoring system state, not for
// synchronization control.
if (readWriteLock.isWriteLocked()) {
lock = readWriteLock.writeLock();
} else {
lock = readWriteLock.readLock();
}
try {
lock.unlock();
} catch (IllegalMonitorStateException e) {
throw new CannotUnlockException("Cannot unlock document " + documentId
+ ": this thread does not own any lock on it", e);
}
// (2) From hasQueuedThreads's javadoc:
// Queries whether any threads are waiting to acquire the read or
// write lock. Note that because cancellations may occur at any
// time, a true return does not guarantee that any other thread will
// ever acquire a lock. This method is designed primarily for use in
// monitoring of the system state.
if (!readWriteLock.hasQueuedThreads() &&
// (3) From getReadLockCount's javadoc:
// Queries the number of read locks held for this lock. This
// method is designed for use in monitoring system state, not
// for synchronization control.
readWriteLock.getReadLockCount() == 0) {
synchronized (activeDocuments) {
Document document = activeDocuments.remove(documentId);
idleDocuments.put(documentId, document);
// Remove the lock from the map to free some space.
readWriteLocks.remove(documentId);
}
}
}
}
protected Lock getLock(ReentrantReadWriteLock lock, LockMode lockMode) {
switch (lockMode) {
case READ:
return lock.readLock();
case WRITE:
return lock.readLock();
default:
throw new IllegalArgumentException("Unknown " + LockMode.class.getName() + ": " + lockMode);
}
}
}
您可能已经在我的代码注释中看到,在方法 unlock
(标记为 (1)、(2)、(3))中有几个点我调用了 Javadoc 指示的方法它们旨在用于监视,而不是用于同步。显然这不行,所以我正在寻找替代方案。有问题的方法检查给定的 ReentrantReadWriteLock
当前是否被锁定以供读取 ((3), getReadLockCount
) 或写入 ((1), isWriteLocked
) 以及是否有线程在排队lock ((2), hasQueuedThreads
——诚然与我当前的 tryLock
场景无关,但我仍想检查一下)。基本上,我想知道有问题的文档是否刚刚从 active 变为 idle (没有人想再使用它了)并且我因此可以将其从 activeDocuments
移动到 idleDocuments
.
我可以在 DocumentProvider
中保持状态(锁定 reading/writing,线程等待),但如果锁具有(或有权访问)必要的信息,我宁愿避免这种情况.我在看上面提到的方法(isWriteLocked
,
getReadLockCount
, hasQueuedThreads
) 在我看来,在我的特定场景中,这些方法应该反映锁的实际状态,因为任何可能修改锁状态的操作总是在synchronized (readWriteLocks) {}
块,因此当我调用其中一种可能不安全的方法时,锁的状态不应改变。
你同意吗?
如果不是,我是否应该将此信息保存在 DocumentProvider
中的锁外部?
顺便说一句,如果有人发现代码有任何同步问题(例如,可能出现死锁),我将不胜感激。
您可以手动实现 ReentrantReadWriteLock
锁,而不是使用系统锁。 (请注意,java 锁期望从之前锁定它的 同一线程 执行解锁。)因为您不使用阻塞锁,所以实现很简单:
class MyReentrantReadWriteLock {
private int state; // 0 - free, -1 - write locked, n>0 - read locked n times
public bool tryLock(LockMode lockMode) {
switch(lockMode) {
case READ:
if(state == -1) return false; // Already write-locked.
state++;
return true;
case WRITE:
if(state) return false; // Already locked.
state = -1;
return true;
}
public void unlock() {
switch(state) {
case 0:
// error: not locked
case -1:
state = 0; // write unlock
break;
default:
state--; // read unlock
}
}
public bool isLocked() {
return state != 0;
}
};
因为您只在 synchronized (readWriteLocks)
下使用锁,所以您的锁实现无需担心同步问题。
UPDATE:如果想用文档锁的更复杂的东西,比如条件变量,最好区分locking ,授予读取或写入访问权限,使用计数,防止文档在使用时被卸载。
使用使用计数器,您需要的 ReentrantReadWriteLock
唯一监控方法是 .isWriteLocked
以确定解锁期间的锁类型。虽然最好从其他地方扣除 属性。
更好的设计将是:
- 将
lockMode
存储在Document
对象中。例如,作为bool m_isWritable;
- 用任何引用计数 class 替换
readWriteLocks
字段定义中的ReentrantReadWriteLock
(并且可能重命名字段本身)。
这允许 DocumentManager
成为真正的工厂对象,它禁止(例如,抛出异常)打开文档,如果它已经作为可写打开(反之亦然)。
另一方面,使用文档的线程可以通过reference将它传递给任何辅助函数,这个函数可以检查文档是否可写:
// User of the document
{
doc = documentManager.openDocument("doc1", WRITE);
//...
doSomethingWithDocument(doc);
//...
documentManager.closeDocument(doc);
}
// Implementation of auxiliary function
void doSomethingWithDocument(Document doc) {
if(!doc.isWritable()) throw new Exception("Read-only document!");
// Process document
}
通过该实现,您不需要重入锁定(所有对 openDocument()
的调用都被视为对 new 文档引用的请求)。至于锁降级,可以用DocumentManager
中的简单方法实现:
void makeReadOnly(Document doc)
{
synchronized(readWriteLocks)
{
doc->m_isWritable = false;
}
}