检查 ReentrantReadWriteLock 的状态(锁定 read/write,线程等待)

Checking the state of a ReentrantReadWriteLock (locked for read/write, threads waiting)

我正在实施 DocumentProvider,它必须支持并发访问它提供的 Document。它的大致工作原理如下:

代码如下(稍微简化):

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,线程等待),但如果锁具有(或有权访问)必要的信息,我宁愿避免这种情况.我在看上面提到的方法(isWriteLockedgetReadLockCount, 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 以确定解锁期间的锁类型。虽然最好从其他地方扣除 属性。

更好的设计将是:

  1. lockMode 存储在 Document 对象中。例如,作为 bool m_isWritable;
  2. 用任何引用计数 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;
     }
}