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;
}
AtomicReferences// 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
LockFreeQueuepublic 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