Возможно создать AtomicReference, который может быть обменен атомарно?

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


В 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 не обязательно разделяются между потоками, поэтому простое их блокирование не будет работать. Они могут быть интернированы, но для внутреннего пула потребуется собственная синхронизация, что станет еще одним узким местом.
  • Я только сделал группы из 2 ссылок здесь, но возможность группировать 3 или более будет бонусом.

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


Связанный: Как я могу атомарно поменять 2 ints в C#?

2 ответа

Решение

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

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 (воспринимайте его как псевдокод без них).

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

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

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

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

Другие вопросы по тегам