Is it possible to implement a (sequentially consistent? linearizable?) queue without locks?
What could go wrong with concurrent enq
?
public void enq (T value) {
Node nd = new Node(value);
tail.next = nd;
tail = nd;
}
public void enq (T value) {
Node nd = new Node(value);
tail.next = nd;
tail = nd;
}
AtomicReference
s// an AtomicReference pointing to someNode
var nd = new AtomicReference<Node>(someNode);
// try to update nd to refer to updated
nd.compareAndSet(expected, update);
Effect of compareAndSet(expected, update)
:
nd
’s current value is expected
, then update value to update
true
nd
’s current value is not expected
, do not update its value
false
When can(‘t) we update tail.next
and tail
?
public void enq (T value) {
Node nd = new Node(value);
tail.next = nd;
tail = nd;
}
To do:
tail.next
to nd
tail
to nd
Under what conditions can we apply these?
tail.next
only if tail.next == null
tail.next
to nd
:
last
to tail
, next
to tail.next
last
is still null
last.next
to nd
only if last.next
is still null
tail
to next
LockFreeQueue
public class LockFreeQueue<T> implements SimpleQueue<T> {
private AtomicReference<Node> head;
private AtomicReference<Node> tail;
...
public void enq(T item) {...}
public T deq() throws EmptyException {...}
class Node {
public T value;
public AtomicReference<Node> next;
...
}
}
enq
public void enq(T item) {
if (item == null) throw new NullPointerException();
Node node = new Node(item);
while (true) {
Node last = tail.get();
Node next = last.next.get();
if (last == tail.get()) {
if (next == null) {
if (last.next.compareAndSet(next, node))
tail.compareAndSet(last, node); return;
} else {
tail.compareAndSet(last, next);}}}}
public void enq(T item) {
if (item == null) throw new NullPointerException();
Node node = new Node(item);
while (true) {
Node last = tail.get();
Node next = last.next.get();
if (last == tail.get()) {
if (next == null) {
if (last.next.compareAndSet(next, node))
tail.compareAndSet(last, node); return;
} else {
tail.compareAndSet(last, next);}}}}
How could we redesign deq
?
public T deq() throws EmptyException {
if (head.next == null){throw new EmptyException();}
value = head.next.value;
head = head.next;
return value;
}
Which lock is “better?”
UnboundedQueue
?LockFreeQueue
?UnboundedQueue
Enqueue public void enq (T value) {
enqLock.lock();
try {
Node nd = new Node(value);
tail.next = nd;
tail = nd;
} finally {
enqLock.unlock();
}
}
LockFreeQueue
Enqueue public void enq(T item) {
if (item == null) throw new NullPointerException();
Node node = new Node(item);
while (true) {
Node last = tail.get();
Node next = last.next.get();
if (last == tail.get()) {
if (next == null) {
if (last.next.compareAndSet(next, node))
tail.compareAndSet(last, node); return;
} else {
tail.compareAndSet(last, next);}}}}
UnboundedQueue
ProgressGuarantee: Starvation Freedom (assuming lock is starvation-free)
if all pending method calls continue to take steps, then every pending method call completes in a finite number of steps
this is blocking progress: if even one thread stops taking steps, then all other threads can be impeded
Question. When is this “good?”
LockFreeQueue
ProgressGuarantee: Lock Freedom
if some pending method call makes progress, then some pending method call completes in a finite number of steps
this is nonblocking progress: if some threads stall, others are still guaranteed to make progress
Question. When is this “good?”
Blocking Progress:
Nonblocking Progress:
Demo: concurrent-queues.zip