Какую стратегию использовать в Java для иерархической повторяющейся блокировки чтения / записи? - PullRequest
7 голосов
/ 27 мая 2011

Я ищу эффективную систему, которая бы имела серию блокировок чтения / записи, организованных иерархически для управления доступом к иерархически организованным ресурсам. Если поддерево заблокировано для записи, тогда никакая другая блокировка не может быть получена во всем поддереве, пока оно не будет освобождено; аналогично, блокировка записи в поддереве должна предотвратить блокировку в родительском элементе.

Вот идеи, которые я обдумывал:

  • Используйте транзакцию Apache Commons . К сожалению, проект не обновлялся с марта 2008 года и был неофициально прекращен. Некоторые документы API, похоже, указывают, что в будущей версии (1.3 или 2.0) будет какая-то иерархическая блокировка , но источники нигде не найдены, и кажется, что мы не можем получить доступ к их репозиторию SVN. больше.

  • Используйте серию ReentrantReadWriteLock с , которую я бы организовал иерархически. Я не эксперт по параллелизму, и я немного боюсь делать это самостоятельно. Предварительные мысли, казалось, указывали на то, что даже прежде чем я смогу попытаться заблокировать поддерево, мне пришлось бы использовать внешнюю блокировку всей структуры, управляющей самими ReentrantReadWriteLock, чтобы даже для снятие блокировки Мне бы пришлось использовать внешний замок ...

  • Используйте классы из java.util.concurrent и java.util.concurrent.atomic, чтобы реализовать мою иерархическую блокировку более эффективным способом, чем я мог бы сделать с серия ReentrantReadWriteLock с.

Я готов идти по этому последнему пути, но я был удивлен, что не нашел какой-либо существующей библиотеки, которая бы лучше решала эту проблему. Итак:

  • Я пропустил какое-то очевидное решение?
  • Или эту проблему просто особенно трудно решить?

Ответы [ 2 ]

4 голосов
/ 27 мая 2011

Я не знаю, правильно ли я понял ваш вопрос, поскольку вы говорите, что когда вы блокируете поддерево для записи, вся структура блокируется. Таким образом, простое решение состоит в том, чтобы иметь одну блокировку 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;
}

Если какая-либо блокировка не может быть получена, то вы разблокируете то, что заблокировали, и повторите попытку позже (для этого можно использовать глобальную переменную условия, тайм-аут и т. Д.).

РЕДАКТИРОВАТЬ: добавлен код для блокировки чтения / записи дерева

0 голосов
/ 27 мая 2011

Я пойду за вашим собственным решением и возьму алгоритм, заданный Apache Алгоритм транзакций Apache Commons в качестве отправной точки. Вы можете использовать ReentrantReadWriteLock, хотя обычно эта блокировка лучше подходит для случая одного производителя-читателя, который может не соответствовать тому, что вы ищете. Кажется, ваша блокировка больше похожа на обычную блокировку повторного входа.

...