Возможно создать AtomicReference, который может быть обменен атомарно? - PullRequest
6 голосов
/ 26 января 2011

Есть ли способ реализовать тип ссылки, значение которой можно атомарно обменивать на другую?


В Java у нас есть AtomicReference, который может быть заменен локальной переменной, но не другойAtomicReference.

Вы можете сделать:

AtomicReference r1 = new AtomicReference("hello");
AtomicReference r2 = new AtomicReference("world");

и поменять их местами с помощью комбинации из двух операций:

r1.set(r2.getAndSet(r1.get()));

Но это оставляет их в несовместимом состояниимежду ними, где оба содержат "hello".Кроме того, даже если вы можете поменять их атомарно, вы все равно не сможете прочитать их (как пару) атомарно.


Я бы хотел иметь возможность:

PairableAtomicReference r1 = new PairableAtomicReference("hello");
PairableAtomicReference r2 = new PairableAtomicReference("world");
AtomicRefPair rp = new AtomicRefPair(r1, r2);

затем

Object[] oldVal, newVal;
do {
    oldVal = rp.get();
    newVal = new Object[] {oldVal[1], oldVal[0]};
} while (! rp.compareAndSet(oldVal, newVal));

, чтобы поменять значения, и в другом потоке:

AtomicRefPair otherRP = new AtomicRefPair(r1, r2);
System.out.println(Arrays.toString(otherRP.get()));

и быть уверенным, что вывод будет либо [hello, world], либо [world, hello].

Примечания:

  • r1 и r2 сопряжены для этой операции, но возможно, что другой поток будет независимо соединяться, скажем, r1 и другой r3 (к сожалению, это означает, чтоЯ не могу использовать это решение .)
  • Будут сотни тысяч этих ссылок, поэтому глобальное ReentrantLock будет основным узким местом.
  • rpи otherRP не обязательно являются общими для потоков, поэтому простое их блокирование не будет работать.Они могут быть interned , но для внутреннего пула потребуется собственная синхронизация, что станет еще одним узким местом.
  • Я сделал только группы из 2 ссылок, но возможность группировать 3 или болеебудет бонусом.

Можно ли реализовать версию без блокировки AtomicRefPair?У меня есть догадка, что это не так, но если нет, то, может быть, где-то есть статья, которая объясняет, почему?


Related : Как атомарно поменять 2целые числа в C #?

Ответы [ 2 ]

4 голосов
/ 26 января 2011

Имейте неизменный класс, держащий пару. Это твой атом. Обмен пары означает замену атома.

обновление: ваш вопрос не очень понятен. но в целом для параллельной системы, состоящей из нескольких переменных, может потребоваться

  1. сделать снимок состояния системы. снимок не изменяется после съемки.
  2. атомное обновление состояния системы путем одновременного изменения нескольких переменных. может потребоваться, чтобы между моим обновлением и предыдущим снимком не было другого обновления (на котором основывались мои вычисления)

вы можете смоделировать вашу систему непосредственно в моментальных снимках, если она не потребляет слишком много ресурсов.

3 голосов
/ 26 января 2011

Я не знаю, есть ли хорошее решение, но может работать следующее безобразное:

public final class MyReference<T> extends ReentrantLock implements Comparable<MyReference<T>> {
    public MyReference() {
        id = counter.incrementAndGet();
    }

    public void swap(MyReference<T> other) {
        if (id < other.id) {
            lock();
            other.lock();
        } else {
            other.lock();
            lock();
        }
        final T tmp = value;
        value = other.value;
        other.value = tmp;
        unlock();
        other.unlock();
    }

    public static <T> List<T> consistentGet(List<MyReference<T>> references) {
        final ArrayList<MyReference<T>> sortedReferences = Lists.newArrayList(references);
        Collections.sort(sortedReferences);
        for (val r : sortedReferences) r.lock();
        final List<T> result = Lists.newArrayListWithExpectedSize(sortedReferences.size());
        for (val r : references) result.add(r.value);
        for (val r : sortedReferences) r.unlock();
        return result;
    }

    @Override
    public int compareTo(MyReference<T> o) {
        return id < o.id ? -1 : id > o.id ? 1 : 0;
    }

    private final static AtomicInteger counter = new AtomicInteger();

    private T value;
    private final int id;
}
  • Используйте MyReference вместо AtomicReference.
  • Он использует много блокировок, но ни один из них не является глобальным.
  • Он получает блокировки в фиксированном порядке, поэтому он не блокируется.
  • Он компилируется с использованием lombok и guava (воспринимайте их как псевдокод без них).
...