【问题标题】:What strategy to use in Java for hierarchical reentrant read/write locking?在 Java 中使用什么策略进行分层可重入读/写锁定?
【发布时间】:2011-05-29 03:02:22
【问题描述】:

我正在寻找一种高效的系统,该系统具有一系列分层组织的读/写锁,以管理对分层组织资源的访问。如果一个子树被锁写,那么在整个子树被释放之前,应该无法获得其他锁;同样,子树中的写锁应该防止锁定父树。

以下是我正在考虑的想法:

  • 使用Apache Commons Transaction。不幸的是,该项目自 2008 年 3 月以来一直没有更新,并已被非正式终止。一些 API 文档似乎表明即将发布的版本(1.3 或 2.0)将包含 some kind of hierarchical locking,但找不到源代码,而且我们似乎无法再访问他们的 SVN 存储库了。

    李>
  • 使用一系列ReentrantReadWriteLocks,我会按层次组织。我不是并发专家,我有点害怕自己做。初步的想法似乎表明,即使在我可以尝试锁定子树之前,我也必须在管理 ReentrantReadWriteLocks 本身的整个结构上使用外锁 - 这样即使 释放锁 我得用外锁……

  • 使用 java.util.concurrentjava.util.concurrent.atomic 中的类以比使用一系列 ReentrantReadWriteLocks 更有效的方式实现我的分层锁。

我已经准备好走最后一条路了,但我很惊讶没有找到任何可以更好地解决这个问题的现有库。所以:

  • 我是否遗漏了一些明显的解决方案?
  • 还是这个问题特别难以正确解决?

【问题讨论】:

  • 远离atomic,正确理解和实施是非常微妙的。
  • 您的第二个解决方案似乎是正确的。当您想要锁定一个节点时,您需要获取全局锁,以便您可以检查该节点的所有子节点都没有被锁定,并且它的父节点也没有被锁定。
  • 对了,Commons Transaction 是available,不过不知道对你有没有用。
  • @toto 感谢您对我的方法发表评论。是的,1.2 版可用,但不包括分层锁的东西。

标签: java locking hierarchical reentrancy readwritelock


【解决方案1】:

我不知道我是否理解您的问题,正如您所说,当您锁定子树进行写入时,整个结构都被锁定。 因此,简单的解决方案是为整个结构设置一个 RW 锁。

顺便说一句,java.util.concurrent.atomic 对你的帮助不只是一棵 RW 锁树。


如果您希望能够独立锁定兄弟姐妹,您可以采用第二种解决方案(锁树,其中每个节点都有对其父节点的引用)。

锁定一个节点将使用其写锁锁定它并使用读锁锁定每个父节点。 当孩子被锁定时,父母不能被锁定,因为您无法获得它的写锁,因为锁定孩子已经获得了读锁。 只有在没有其他线程写锁定任何父线程时才允许锁定子线程。

上述锁是排他锁。 (读写锁的另一个名称是共享独占锁)

要添加共享锁,每个节点还需要一个原子整数,表示: 如果它是正数,间接写锁定子的数量;如果它是负数,则节点已被读锁定。 此外,节点及其父节点将被读锁定,以避免在父节点上获得新的写锁。

伪代码:

Node {
    // fields
    parent: Node
    lock: RWLock
    count: AtomicInteger
}

public boolean trylocktree(node: Node, exclusive: boolean) {
    if (exclusive) {
        return trylocktree_ex(node, true);
    } else {
        return trylocktree_sh(node);
    }
}
private boolean switch_count(i: AtomicInteger, diff: int) {
    // adds diff to i if the sign of i is the same as the sign of diff
    while (true) {
        int v = i.get();
        if (diff > 0 ? v < 0 : v > 0)
            return false;
        if (i.compareAndSet(v, v + diff))
            return true;
    }
}
private boolean trylocktree_ex(node: Node, writing: boolean) {
    // check if a node is read-locked
    if (!switch_count(node.count, 1))
        return false;
    // lock using the lock type passed as an arg
    if (!node.lock(writing).trylock()) {
        node.count--;
        return false;
    }
    // read-lock every parent
    if (!trylocktree_ex(node.parent, false)) {
        node.count--
        node.lock(writing).unlock();
        return false;
    }
    return true;
}
private boolean trylocktree_sh(node: Node) {
    // mark as shared-locked subtree
    if (!switch_count(node.count, -1))
        return false;
    // get shared-lock on parents
    if (!readlock_recursively(node)) {
        node.count++;
        return false;
    }
    return true;
}
private boolean readlock_recursively(node: Node) {
    if (!node.lock(false).trylock())
        return false;
    if (!readlock_recursively(node.parent)) {
        node.lock(false).unlock();
        return false;
    }
    return true;
}

如果无法获得任何锁,则解锁已锁定的内容并稍后重试(您可以使用全局条件变量、超时等来实现此目的)。

编辑:添加了读锁/写锁树的代码

【讨论】:

  • 感谢您的补充说明。如果我写锁定一个孩子,我不确定是否足以读锁定父母,因为有人可能想要读锁定父母本身并期望所有孩子的一致阅读),这是行不通的因为写锁定的孩子会同时更新......
  • 我这里描述的锁是这种结构的可重入锁的实现,而不是读写锁。我正在研究读/写锁定解决方案。
  • 非常感谢您的更新。仍然:似乎在您的实现中,如果我写锁定一个孩子,另一个线程仍然可以读锁定父级,这是不幸的。看起来我们需要另一个计数器:写锁定后代的数量或类似的东西。
  • 如果一个孩子被写锁定,它也为每个父母持有读锁,每个父母的count 都是积极的。由于读锁定需要将count改为负值,如果count已经严格为正,就会失败。
【解决方案2】:

我将寻求您自己的解决方案,并以 Apache Apache Commons Transaction Algorithm 给出的算法为起点。 您可以使用 ReentrantReadWriteLock,尽管通常此锁更适合一个生产者 - 许多读者的情况,这可能不是您想要的。看来您的锁更像是普通的可重入互斥锁。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多