Use AtomicMarkableReference<Node> for fields
mark indicates logical removalFor add/remove:
remove)compareAndSet to atomically
next field of predecessorFor contains:
Traverse are remove:
pred and curr where item livesReturn: a Window
class Window {
public Node pred;
public Node curr;
public Window (Node pred, Node curr) {
this.pred = pred;
this.curr = curr;
}
}
public Window find(Node head, long key) {
Node pred = null;
Node curr = null;
Node succ = null;
boolean[] marked = {false};
boolean snip;
retry: while(true) {
pred = head;
curr = pred.next.getReference();
while(true) {
succ = curr.next.get(marked);
while(marked[0]) {
snip = pred.next.compareAndSet(curr, succ, false, false);
if (!snip) continue retry;
curr = pred.next.getReference();
succ = curr.next.get(marked);
}
if (curr.key >= key) {
return new Window(pred, curr);
}
pred = curr;
curr = succ;
}
}
public boolean remove(T item) {
int key = item.hashCode();
boolean snip;
while (true) {
Window window = find(head, key);
Node pred = window.pred;
Node curr = window.curr;
if (curr.key != key) {
return false;
}
Node succ = curr.next.getReference();
snip = curr.next.compareAndSet(succ, succ, false, true);
if (!snip) {
continue;
}
pred.next.compareAndSet(curr, succ, false, false);
return true;
}
}
Runtimes: 1M Operations
| n elts | coarse | fine | optimistic | lazy | non-block |
|---|---|---|---|---|---|
| 10 | 0.11 s | 0.14 s | 0.13 s | 0.11 s | 0.11 s |
| 100 | 0.14 s | 0.46 s | 0.19 s | 0.13 s | 0.20 s |
| 1000 | 1.1 s | 3.9 s | 2.2 s | 1.1 s | 2.8 s |
| 10000 | 28 s | 39 s | 59 s | 29 s | 66 s |
Runtimes: 1M Operations
| n elts | coarse | fine | optimistic | lazy | non-block |
|---|---|---|---|---|---|
| 10 | 0.21 s | 0.36 s | 0.33 s | 0.33 s | 0.21 s |
| 100 | 0.27 s | 1.80 s | 0.38 s | 0.12 s | 0.07 s |
| 1000 | 1.8 s | 4.7 s | 0.86 s | 0.19 s | 0.49 s |
| 10,000 | 32 s | 17 s | 9.2 s | 4.7 s | 9.2 s |
Note: fewer elements $\implies$ greater contention
Many ways of achieving correct behavior
Tradeoffs:
Recently:
Questions:
Pool<T> InterfaceWant to model producer/consumer problem
A pool stores items between production and consumption:
public interface Pool<T> {
void put(T item); // add item to the pool
T get(); // remove item from pool
}
get from empty pool waits for a put
put to full pool waits for a get
put call waits for get
Can use a queue to implement a pool:
enq implements put
deq implements get
Two queue implementations today
Node head sentinal
deq returns head.next value (if any), updates head
Node tail
enq updates tail.next, updates tail
enqLock locks enq operationdeqLock locks deq operationNodes are not locked
deqLock


head



enqLock

tail.next

tail


UnboundedQueue in Codepublic class UnboundedQueue<T> implements SimpleQueue<T> {
final ReentrantLock enqLock;
final ReentrantLock deqLock;
volatile Node head;
volatile Node tail;
public UnboundedQueue() {
head = new Node(null);
tail = head;
enqLock = new ReentrantLock();
deqLock = new ReentrantLock();
}
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;
}
public void enq (T value) {
enqLock.lock();
try {
Node nd = new Node(value);
tail.next = nd;
tail = nd;
} finally {
enqLock.unlock();
}
}
class Node {
final T value;
volatile Node next;
public Node (T value) {
this.value = value;
}
}
}
Easy to reason about:
enq
enqLock
deq
deqLock
What about concurrent calls to enq/deq?