我正在实现 DocumentProvider
必须支持并发访问 Document
它提供了。其大致工作原理如下:
- 文档按需加载。
- 使用文档时,它保存在
activeDocuments
中(aMap<String, Document>
按文档 ID 索引)。 - 文档停止使用后,将从
activeDocuments
移出至idleDocuments
(也是一个Map<String, Document>
按文档 ID 索引)。idleDocuments
是一个 LRU 缓存,这意味着在某个时刻Document
此缓存中的内容可能会被丢弃。 - 每个文档都可以按照通常的读者/作者政策进行阅读或写作。这是使用
ReentrantReadWriteLock
实现的每个活跃Document
. - 尝试锁定文档是非锁定的:线程将调用
tryLock
而不是lock
。如果无法锁定文档,则会出现CannotLockException
被抛出,线程将不得不处理它——到底如何处理超出了这个问题的范围。 (示例:显示消息“文档当前已锁定。您想打开只读副本吗?”)
这是代码(稍微简化):
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
)以及是否有线程在锁上排队((2), hasQueuedThreads
——诚然与我当前的 tryLock
场景无关,但我仍然想检查)。基本上,我想知道相关文档是否刚刚从 Activity 变为空闲(没有人想再使用它),因此我可以将其从 activeDocuments
移走。至idleDocuments
.
我可以将状态(锁定读/写,线程等待)保留在 DocumentProvider
中,但如果锁具有(或有权访问)必要的信息,我宁愿避免这种情况。我正在查看上面提到的方法( isWriteLocked
,
getReadLockCount
, hasQueuedThreads
)并且在我看来,在我的特定场景中,这些方法应该反射(reflect)锁的实际状态,因为任何可能修改锁状态的操作总是在 synchronized (readWriteLocks) {}
内执行。 block ,因此当我调用这些潜在不安全方法之一时,锁的状态不应更改。
你同意吗?
如果没有,我是否应该将此信息保留在锁外部,在 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;
}
};
因为您仅在同步(readWriteLocks)
下使用锁,所以您的锁实现无需担心同步。
更新:如果您想对文档的锁使用更复杂的内容(例如条件变量),最好区分锁定(授予读或写访问权限)和使用计数(防止文档在使用时被卸载)。
有了使用计数器,您需要的ReentrantReadWriteLock
的唯一监控方法是.isWriteLocked
,用于在解锁期间确定锁定类型。不过最好从其他地方扣除该属性(property)。
更好的设计将是:
- 将
lockMode
存储在Document
对象中。例如,如bool m_isWritable;
- 将
readWriteLocks
字段定义中的ReentrantReadWriteLock
替换为任何引用计数类(并且可能重命名字段本身)。
这允许 DocumentManager
成为真正的工厂对象,如果文档已经以可写方式打开,则禁止(例如抛出异常)打开文档(反之亦然)。
从另一边来看,使用 document 的线程可以通过引用将其传递给任何辅助函数,并且该函数可以检查 document 是否可写:
// 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()
的所有调用都被视为对新文档引用的请求)。至于锁降级,可以在DocumentManager
中以简单的方法实现:
void makeReadOnly(Document doc)
{
synchronized(readWriteLocks)
{
doc->m_isWritable = false;
}
}
关于java - 检查 ReentrantReadWriteLock 的状态(锁定读/写,线程等待),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31110865/