Pool interface:
void put(T item)T get()enq and deq methods (not nodes)
deqLock


head



enqLock

tail.next

tail


Easy to reason about:
enq
enqLock
deq
deqLock
What about concurrent calls to enq/deq?
enq/deq
head and tail refer to different nodes

deq checks for head.next
enq updates tail.next (= head.next)Who wins?
enq()
public void enq (T value) {
enqLock.lock();
try {
Node nd = new Node(value);
tail.next = nd;
tail = nd;
} finally {
enqLock.unlock();
}
}
deq()
public T deq() throws EmptyException {
T value;
deqLock.lock();
try {
if (head.next == null) {
throw new EmptyException();
}
value = head.next.value;
head = head.next;
} finally {
deqLock.unlock();
}
return value;
}
enq linearizes to
tail.next = nd
tail is updateddeq linearizes to
head.next == null
head is updatedSo:
head and tail don’t always point to first (predecessor) and last items in the queueWhy is this not a problem?
head and tail don’t always point to first (predecessor) and last items in the queue?
Not a problem because
enq/deq operationstail/head updated before lock releasedenq cares about tail value
tail updateddeq cares about head value
head udpated…we’ve got a queue with locks…
Use AtomicReferences for head/tail/next
compareAndSet
enq
Node nd = new Node(item);
Node last = tail.get();
Node next = last.next.get();
if (next == null) {
if (last.next.compareAndSet(next, node)) {
tail.compareAndSet(last, node);
}
}
The subtelty
tail and tail.get().next atomicallytail will not refer to last node in listenq and deq must function properly even if:
head and tail don’t point where they shouldenq and deq operations are in progress
Clean up each others’ messes!
enq can detect if tail isn’t correct
tail.get().next != nullNode last = tail.get();
Node next = last.next.get();
if (next != null) {
tail.compareAndSet(last, next);
}
Threads helping each other!
LockFreeQueuepublic class LockFreeQueue<T> implements SimpleQueue<T> {
private AtomicReference<Node> head;
private AtomicReference<Node> tail;
public LockFreeQueue() {
Node sentinel = new Node(null);
this.head = new AtomicReference<Node>(sentinel);
this.tail = new AtomicReference<Node>(sentinel);
}
public void enq(T item) {...}
public T deq() throws EmptyException {...}
class Node {
public T value;
public AtomicReference<Node> next;
public Node(T value) {
this.value = value;
this.next = new AtomicReference<Node>(null);
}
}
}
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 T deq() throws EmptyException {
while (true) {
Node first = head.get();
Node last = tail.get();
Node next = first.next.get();
if (first == head.get()) {
if (first == last) {
if (next == null) {
throw new EmptyException();
}
tail.compareAndSet(last, next);
} else {
T value = next.value;
if (head.compareAndSet(first, next))
return value;
}
}
}
}
Let’s test the implementations!