Оказывается, это действительно сложная проблема! (Хорошо!) Использование глобальной блокировки было бы слишком просто и, вероятно, слишком медленно. Я думаю, что у меня есть версия без блокировки, о которой я расскажу ниже, но я бы не поверил, что она идеальна. Трудно рассуждать о всех возможных чередованиях.
Как оказалось, это идеальный вариант использования для транзакционной памяти ! Просто пометьте весь блок как атомарный и измените все, что хотите Вы могли бы посмотреть на Двойка STM , хотя я не знаю, как быстро это может быть. Если бы только лучшим системам не требовалось нестандартного оборудования ...
Во всяком случае, подумав над этой проблемой некоторое время, я думаю, что нашел версию, которая обходит блокировки, используя Java AtomicReference . Сначала код:
class Foo {
private AtomicReference<Foo> oRef = new AtomicReference<Foo>;
private static final AtomicInteger order = new AtomicInteger(0);
private final int id = order.incrementAndGet();
private static bool break(Foo x, Foo y) {
if (x.id > y.id)
return break(y, x);
return x.oRef.compareAndSet(y, null) &&
y.oRef.compareAndSet(x, null);
}
public void setOther(Foo f) {
if (f != null && f.id > id) {
f.setOther(this);
return;
}
do {
Foo other = oRef.get();
if (other == f)
break;
if (other != null && !break(this, other))
continue;
if (f == null)
break;
Foo fother = f.oRef.get();
if (fother != null && !break(f, fother))
continue;
if (!f.oRef.compareAndSet(null, this))
continue;
if (!oRef.compareAndSet(null, f)) {
f.oRef.set(null);
continue;
}
} while (false);
}
}
Ключевые моменты:
- Если нет одновременного доступа к любому из затронутых Foos (не более 4), установщик делает один проход через цикл, изменяя соответствующие указатели.
- При наличии одновременных сеттеров некоторые из сеттеров могут выйти из строя и повторить попытку.
- Если несколько потоков одновременно пытаются разорвать отношения, только один поток преуспеет в выполнении
x.oRef.compareAndSet(y, null)
.
- Если
f.oRef.compareAndSet(null, f)
завершится успешно, никакой другой поток не сможет разорвать наполовину установленную связь в break()
. Затем, если oRef.compareAndSet(null, f)
успешно, операция завершена. Если это не удается, f.oRef
может быть сброшено, и все повторяют попытки.