Я написал реализацию очереди без блокировки Java. У него есть ошибка параллелизма. Я не могу найти это. Этот код не важен. Я просто беспокоюсь о том, что не могу объяснить наблюдаемое поведение, связанное с изменчивыми переменными.
Баг виден по исключению ("null head"). Это невозможное состояние, потому что существует атомарное целое число, содержащее текущий размер очереди. Очередь имеет элемент-заглушку. Он предусматривает, что поток чтения не изменяет хвостовой указатель, а поток записи не изменяет указатель начала.
Переменная длины очереди гарантирует, что связанный список никогда не будет пустым. Это как семафор.
Метод take ведет себя так, как будто получает украденное значение длины.
class Node<T> {
final AtomicReference<Node<T>> next = new AtomicReference<Node<T>>();
final T ref;
Node(T ref) {
this.ref = ref;
}
}
public class LockFreeQueue<T> {
private final AtomicInteger length = new AtomicInteger(1);
private final Node stub = new Node(null);
private final AtomicReference<Node<T>> head = new AtomicReference<Node<T>>(stub);
private final AtomicReference<Node<T>> tail = new AtomicReference<Node<T>>(stub);
public void add(T x) {
addNode(new Node<T>(x));
length.incrementAndGet();
}
public T takeOrNull() {
while (true) {
int l = length.get();
if (l == 1) {
return null;
}
if (length.compareAndSet(l, l - 1)) {
break;
}
}
while (true) {
Node<T> r = head.get();
if (r == null) {
throw new IllegalStateException("null head");
}
if (head.compareAndSet(r, r.next.get())) {
if (r == stub) {
stub.next.set(null);
addNode(stub);
} else {
return r.ref;
}
}
}
}
private void addNode(Node<T> n) {
Node<T> t;
while (true) {
t = tail.get();
if (tail.compareAndSet(t, n)) {
break;
}
}
if (t.next.compareAndSet(null, n)) {
return;
}
throw new IllegalStateException("bad tail next");
}
}