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 nodesdeq
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 != null
Node last = tail.get();
Node next = last.next.get();
if (next != null) {
tail.compareAndSet(last, next);
}
Threads helping each other!
LockFreeQueue
public 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!