2011-05-27 44 views
7

我正在寻找有效的系统,让一系列读/写锁按层次组织,以管理对分层组织资源的访问。如果一个子树被锁定写入,那么在整个子树中都不能获得其他锁,直到它被释放;同样,子树中的写入锁应该防止锁定在父项中。Java中用于等级重入读/写锁定的策略是什么?

这里是我正在考虑的想法:

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

  • 使用一系列ReentrantReadWriteLocks,我会分层组织。我不是并发专家,我有点害怕自己做这件事。初步的想法似乎表明,即使在我可以尝试锁定一个子树之前,我也不得不在整个管理ReentrantReadWriteLock的结构上使用外部锁 - 因此,即使对于释放锁,我也必须使用外锁......从java.util.concurrentjava.util.concurrent.atomic

  • 使用类来实现我的等级锁定在一个更有效的方式比我能有一系列ReentrantReadWriteLock就做。

我准备走这最后的路,但我很惊讶,没找到,更好地解决这个问题的任何退出库。所以:

  • 我错过了一些明显的解决方案?
  • 或者这个问题只是特别难以妥善解决?
+0

远离“原子”,正确理解和实施是非常微妙的。 – toto2 2011-05-28 14:19:08

+1

你的第二个解决方案似乎是正确的。当需要锁定节点时,需要获取全局锁,以便可以检查该节点的所有子节点都未锁定,并且没有锁定其父节点。 – toto2 2011-05-28 14:32:45

+0

顺便说一下,Commons Transaction是[available](http://commons.apache.org/transaction/download_transaction.cgi),但我不知道它对你是否有用。 – toto2 2011-05-28 14:36:46

回答

3

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

顺便说一句,java.util.concurrent.atomic不会帮助你超过RW锁的树。


如果你希望能够锁定的兄弟姐妹independly,你可以与第二溶液(锁的树,其中每个节点有其父的引用)去。

锁定一个节点将使用它的写锁来锁定它,并使用读锁来锁定每个父节点。 因为您无法获取其写入锁定,因此锁定已获取读取锁定的子项时,父级无法在子级锁定。 只有在没有其他线程已经写入锁定任何父项的情况下,才允许锁定子项。

上述的锁是独占锁。 (对于读 - 写锁的另一个名称是共享的独占锁)

要添加共享锁,每个节点还需要的原子整数,指示: 如果它是正的,间接的写锁定儿童的数量;如果它是否为节点已被读锁定的次数。 此外,节点及其父母将被读取锁定,以避免父母获取新的写锁。

的伪代码:

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; 
} 

如果任何锁定,因此无法获取,那么你解开你已锁定并稍后重试(你可以使用全局条件变量,超时等,以实现这个)。

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

+0

感谢您的补充说明。如果我锁定孩子,我不确定是否已经足够锁定父母,因为有人可能想要自行读取锁定父母并期望对所有孩子进行一致的阅读),并且这样做不起作用因为写锁定的孩子会同时更新... – 2011-05-28 15:32:34

+0

我在这里描述的锁是这种结构的可重入锁的实现,而不是读写锁。我正在研究读/写锁定解决方案。 – Kru 2011-05-28 18:19:58

+0

非常感谢您的更新。尽管如此,看来在你的实现中,如果我写锁定一个孩子,另一个线程仍然可以读取锁定父母,这是不幸的。看起来我们需要另一个计数器:写锁定后代的数量或类似的东西。 – 2011-05-30 14:37:46

0

我会去为自己的解决方案,并采取由阿帕奇Apache Commons Transaction Algorithm给为出发点的算法。 你可以使用ReentrantReadWriteLock,尽管通常这个锁更适合一个生产者的模式 - 很多读者可能不是你想要的东西。看起来你的锁更像是一个普通的可重入互斥锁。