java - 检查 ReentrantReadWriteLock 的状态(锁定读/写,线程等待)

标签 java multithreading concurrency locking

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

  • 文档按需加载。
  • 使用文档时,它保存在 activeDocuments 中(a Map<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 中,但如果锁具有(或有权访问)必要的信息,我宁愿避免这种情况。我正在查看上面提到的方法( isWriteLockedgetReadLockCount , 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)。

更好的设计将是:

  1. lockMode存储在Document对象中。例如,如 bool m_isWritable;
  2. 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/

相关文章:

java - 什么是NullPointerException,我该如何解决?

java - 如何定制测试报告?

java - Realm - 如何构建具有多种类型对象的提要?

启动进程时 Java Android ping IOException "Too many files open"

python - 从子线程停止主线程

python - 每 n 秒运行一次特定代码

java - 为什么此代码会抛出 java ConcurrentModificationException?

java - Java 程序中可能的文件操作?

c# - 什么能比并发集合更好地解决这种多线程场景

go - 在监视器 goroutine 中递归发送