6981113: Add ConcurrentLinkedDeque

Extend techniques developed for ConcurrentLinkedQueue and LinkedTransferQueue to implement a non-blocking concurrent Deque with interior removes.

Reviewed-by: martin, dholmes, chegar
This commit is contained in:
Doug Lea 2010-09-20 18:05:09 -07:00
parent 11bbc5d5bc
commit 94ffffd5cb
13 changed files with 1689 additions and 141 deletions

View File

@ -272,6 +272,7 @@ JAVA_JAVA_java = \
java/util/concurrent/CancellationException.java \
java/util/concurrent/CompletionService.java \
java/util/concurrent/ConcurrentHashMap.java \
java/util/concurrent/ConcurrentLinkedDeque.java \
java/util/concurrent/ConcurrentLinkedQueue.java \
java/util/concurrent/ConcurrentMap.java \
java/util/concurrent/ConcurrentNavigableMap.java \

File diff suppressed because it is too large Load Diff

View File

@ -28,9 +28,9 @@
* However, the following notice accompanied the original version of this
* file:
*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
* Written by Doug Lea and Martin Buchholz with assistance from members of
* JCP JSR-166 Expert Group and released to the public domain, as explained
* at http://creativecommons.org/licenses/publicdomain
*/
package java.util.concurrent;
@ -53,7 +53,8 @@ import java.util.Queue;
* operations obtain elements at the head of the queue.
* A {@code ConcurrentLinkedQueue} is an appropriate choice when
* many threads will share access to a common collection.
* This queue does not permit {@code null} elements.
* Like most other concurrent collection implementations, this class
* does not permit the use of {@code null} elements.
*
* <p>This implementation employs an efficient &quot;wait-free&quot;
* algorithm based on one described in <a
@ -61,14 +62,20 @@ import java.util.Queue;
* Fast, and Practical Non-Blocking and Blocking Concurrent Queue
* Algorithms</a> by Maged M. Michael and Michael L. Scott.
*
* <p>Iterators are <i>weakly consistent</i>, returning elements
* reflecting the state of the queue at some point at or since the
* creation of the iterator. They do <em>not</em> throw {@link
* ConcurrentModificationException}, and may proceed concurrently with
* other operations. Elements contained in the queue since the creation
* of the iterator will be returned exactly once.
*
* <p>Beware that, unlike in most collections, the {@code size} method
* is <em>NOT</em> a constant-time operation. Because of the
* asynchronous nature of these queues, determining the current number
* of elements requires a traversal of the elements.
*
* <p>This class and its iterator implement all of the
* <em>optional</em> methods of the {@link Collection} and {@link
* Iterator} interfaces.
* <p>This class and its iterator implement all of the <em>optional</em>
* methods of the {@link Queue} and {@link Iterator} interfaces.
*
* <p>Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
@ -132,9 +139,10 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
*
* Both head and tail are permitted to lag. In fact, failing to
* update them every time one could is a significant optimization
* (fewer CASes). This is controlled by local "hops" variables
* that only trigger helping-CASes after experiencing multiple
* lags.
* (fewer CASes). As with LinkedTransferQueue (see the internal
* documentation for that class), we use a slack threshold of two;
* that is, we update head/tail when the current pointer appears
* to be two or more steps away from the first/last node.
*
* Since head and tail are updated concurrently and independently,
* it is possible for tail to lag behind head (why not)?
@ -148,8 +156,8 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
* this is merely an optimization.
*
* When constructing a Node (before enqueuing it) we avoid paying
* for a volatile write to item by using lazySet instead of a
* normal write. This allows the cost of enqueue to be
* for a volatile write to item by using Unsafe.putObject instead
* of a normal write. This allows the cost of enqueue to be
* "one-and-a-half" CASes.
*
* Both head and tail may or may not point to a Node with a
@ -161,38 +169,25 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
*/
private static class Node<E> {
private volatile E item;
private volatile Node<E> next;
volatile E item;
volatile Node<E> next;
/**
* Constructs a new node. Uses relaxed write because item can
* only be seen after publication via casNext.
*/
Node(E item) {
// Piggyback on imminent casNext()
lazySetItem(item);
}
E getItem() {
return item;
UNSAFE.putObject(this, itemOffset, item);
}
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
void setItem(E val) {
item = val;
}
void lazySetItem(E val) {
UNSAFE.putOrderedObject(this, itemOffset, val);
}
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
Node<E> getNext() {
return next;
}
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
@ -219,7 +214,7 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
* - it is permitted for tail to lag behind head, that is, for tail
* to not be reachable from head!
*/
private transient volatile Node<E> head = new Node<E>(null);
private transient volatile Node<E> head;
/**
* A node from which the last node on list (that is, the unique
@ -233,25 +228,41 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
* to not be reachable from head!
* - tail.next may or may not be self-pointing to tail.
*/
private transient volatile Node<E> tail = head;
private transient volatile Node<E> tail;
/**
* Creates a {@code ConcurrentLinkedQueue} that is initially empty.
*/
public ConcurrentLinkedQueue() {}
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
/**
* Creates a {@code ConcurrentLinkedQueue}
* initially containing the elements of the given collection,
* added in traversal order of the collection's iterator.
*
* @param c the collection of elements to initially contain
* @throws NullPointerException if the specified collection or any
* of its elements are null
*/
public ConcurrentLinkedQueue(Collection<? extends E> c) {
for (E e : c)
add(e);
Node<E> h = null, t = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
// Have to override just to update the javadoc
@ -266,13 +277,6 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
return offer(e);
}
/**
* We don't bother to update head or tail pointers if fewer than
* HOPS links from "true" location. We assume that volatile
* writes are significantly more expensive than volatile reads.
*/
private static final int HOPS = 1;
/**
* Try to CAS head to p. If successful, repoint old head to itself
* as sentinel for succ(), below.
@ -288,7 +292,7 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
* stale pointer that is now off the list.
*/
final Node<E> succ(Node<E> p) {
Node<E> next = p.getNext();
Node<E> next = p.next;
return (p == next) ? head : next;
}
@ -299,68 +303,75 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
Node<E> n = new Node<E>(e);
retry:
for (;;) {
Node<E> t = tail;
Node<E> p = t;
for (int hops = 0; ; hops++) {
Node<E> next = succ(p);
if (next != null) {
if (hops > HOPS && t != tail)
continue retry;
p = next;
} else if (p.casNext(null, n)) {
if (hops >= HOPS)
casTail(t, n); // Failure is OK.
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
} else {
p = succ(p);
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
public E poll() {
Node<E> h = head;
Node<E> p = h;
for (int hops = 0; ; hops++) {
E item = p.getItem();
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
if (hops >= HOPS) {
Node<E> q = p.getNext();
updateHead(h, (q != null) ? q : p);
if (item != null && p.casItem(item, null)) {
// Successful CAS is the linearization point
// for item to be removed from this queue.
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
return item;
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
Node<E> next = succ(p);
if (next == null) {
updateHead(h, p);
break;
}
p = next;
}
return null;
}
public E peek() {
Node<E> h = head;
Node<E> p = h;
E item;
restartFromHead:
for (;;) {
item = p.getItem();
if (item != null)
break;
Node<E> next = succ(p);
if (next == null) {
break;
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null || (q = p.next) == null) {
updateHead(h, p);
return item;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
p = next;
}
updateHead(h, p);
return item;
}
/**
@ -372,24 +383,20 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
* of losing a race to a concurrent poll().
*/
Node<E> first() {
Node<E> h = head;
Node<E> p = h;
Node<E> result;
restartFromHead:
for (;;) {
E item = p.getItem();
if (item != null) {
result = p;
break;
for (Node<E> h = head, p = h, q;;) {
boolean hasItem = (p.item != null);
if (hasItem || (q = p.next) == null) {
updateHead(h, p);
return hasItem ? p : null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
Node<E> next = succ(p);
if (next == null) {
result = null;
break;
}
p = next;
}
updateHead(h, p);
return result;
}
/**
@ -410,18 +417,20 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
* <em>NOT</em> a constant-time operation. Because of the
* asynchronous nature of these queues, determining the current
* number of elements requires an O(n) traversal.
* Additionally, if elements are added or removed during execution
* of this method, the returned result may be inaccurate. Thus,
* this method is typically not very useful in concurrent
* applications.
*
* @return the number of elements in this queue
*/
public int size() {
int count = 0;
for (Node<E> p = first(); p != null; p = succ(p)) {
if (p.getItem() != null) {
// Collections.size() spec says to max out
for (Node<E> p = first(); p != null; p = succ(p))
if (p.item != null)
// Collection.size() spec says to max out
if (++count == Integer.MAX_VALUE)
break;
}
}
return count;
}
@ -436,9 +445,8 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
public boolean contains(Object o) {
if (o == null) return false;
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.getItem();
if (item != null &&
o.equals(item))
E item = p.item;
if (item != null && o.equals(item))
return true;
}
return false;
@ -459,7 +467,7 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
if (o == null) return false;
Node<E> pred = null;
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.getItem();
E item = p.item;
if (item != null &&
o.equals(item) &&
p.casItem(item, null)) {
@ -473,6 +481,69 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
return false;
}
/**
* Appends all of the elements in the specified collection to the end of
* this queue, in the order that they are returned by the specified
* collection's iterator. Attempts to {@code addAll} of a queue to
* itself result in {@code IllegalArgumentException}.
*
* @param c the elements to be inserted into this queue
* @return {@code true} if this queue changed as a result of the call
* @throws NullPointerException if the specified collection or any
* of its elements are null
* @throws IllegalArgumentException if the collection is this queue
*/
public boolean addAll(Collection<? extends E> c) {
if (c == this)
// As historically specified in AbstractQueue#addAll
throw new IllegalArgumentException();
// Copy c into a private chain of Nodes
Node<E> beginningOfTheEnd = null, last = null;
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (beginningOfTheEnd == null)
beginningOfTheEnd = last = newNode;
else {
last.lazySetNext(newNode);
last = newNode;
}
}
if (beginningOfTheEnd == null)
return false;
// Atomically append the chain at the tail of this collection
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, beginningOfTheEnd)) {
// Successful CAS is the linearization point
// for all elements to be added to this queue.
if (!casTail(t, last)) {
// Try a little harder to update tail,
// since we may be adding many elements.
t = tail;
if (last.next == null)
casTail(t, last);
}
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
/**
* Returns an array containing all of the elements in this queue, in
* proper sequence.
@ -490,7 +561,7 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
// Use ArrayList to deal with resizing.
ArrayList<E> al = new ArrayList<E>();
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.getItem();
E item = p.item;
if (item != null)
al.add(item);
}
@ -539,7 +610,7 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
int k = 0;
Node<E> p;
for (p = first(); p != null && k < a.length; p = succ(p)) {
E item = p.getItem();
E item = p.item;
if (item != null)
a[k++] = (T)item;
}
@ -552,7 +623,7 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
// If won't fit, use ArrayList version
ArrayList<E> al = new ArrayList<E>();
for (Node<E> q = first(); q != null; q = succ(q)) {
E item = q.getItem();
E item = q.item;
if (item != null)
al.add(item);
}
@ -561,7 +632,9 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
/**
* Returns an iterator over the elements in this queue in proper sequence.
* The returned iterator is a "weakly consistent" iterator that
* The elements will be returned in order from first (head) to last (tail).
*
* <p>The returned {@code Iterator} is a "weakly consistent" iterator that
* will never throw {@link java.util.ConcurrentModificationException
* ConcurrentModificationException},
* and guarantees to traverse elements as they existed upon
@ -620,7 +693,7 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
nextItem = null;
return x;
}
E item = p.getItem();
E item = p.item;
if (item != null) {
nextNode = p;
nextItem = item;
@ -648,13 +721,13 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
Node<E> l = lastRet;
if (l == null) throw new IllegalStateException();
// rely on a future traversal to relink.
l.setItem(null);
l.item = null;
lastRet = null;
}
}
/**
* Save the state to a stream (that is, serialize it).
* Saves the state to a stream (that is, serializes it).
*
* @serialData All of the elements (each an {@code E}) in
* the proper order, followed by a null
@ -668,7 +741,7 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
// Write out all elements in the proper order.
for (Node<E> p = first(); p != null; p = succ(p)) {
Object item = p.getItem();
Object item = p.item;
if (item != null)
s.writeObject(item);
}
@ -678,25 +751,40 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
}
/**
* Reconstitute the Queue instance from a stream (that is,
* deserialize it).
* Reconstitutes the instance from a stream (that is, deserializes it).
* @param s the stream
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in capacity, and any hidden stuff
s.defaultReadObject();
head = new Node<E>(null);
tail = head;
// Read in all elements and place in queue
for (;;) {
// Read in elements until trailing null sentinel found
Node<E> h = null, t = null;
Object item;
while ((item = s.readObject()) != null) {
@SuppressWarnings("unchecked")
E item = (E)s.readObject();
if (item == null)
break;
else
offer(item);
Node<E> newNode = new Node<E>((E) item);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
/**
* Throws NullPointerException if argument is null.
*
* @param v the element
*/
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
// Unsafe mechanics
@ -715,10 +803,6 @@ public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
}
private void lazySetHead(Node<E> val) {
UNSAFE.putOrderedObject(this, headOffset, val);
}
static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
String field, Class<?> klazz) {
try {

View File

@ -173,6 +173,11 @@ public class BiggernYours {
new ConcurrentHashMap() {
public int size() {return randomize(super.size());}});
testCollections(
new ConcurrentLinkedDeque(),
new ConcurrentLinkedDeque() {
public int size() {return randomize(super.size());}});
testCollections(
new ConcurrentLinkedQueue(),
new ConcurrentLinkedQueue() {

View File

@ -48,6 +48,7 @@ public class IteratorAtEnd {
testCollection(new PriorityQueue());
testCollection(new LinkedBlockingQueue());
testCollection(new ArrayBlockingQueue(100));
testCollection(new ConcurrentLinkedDeque());
testCollection(new ConcurrentLinkedQueue());
testCollection(new LinkedTransferQueue());

View File

@ -75,6 +75,7 @@ public class MOAT {
testCollection(new ArrayBlockingQueue<Integer>(20));
testCollection(new LinkedBlockingQueue<Integer>(20));
testCollection(new LinkedBlockingDeque<Integer>(20));
testCollection(new ConcurrentLinkedDeque<Integer>());
testCollection(new ConcurrentLinkedQueue<Integer>());
testCollection(new LinkedTransferQueue<Integer>());
testCollection(new ConcurrentSkipListSet<Integer>());
@ -431,8 +432,9 @@ public class MOAT {
q.poll();
equal(q.size(), 4);
checkFunctionalInvariants(q);
if ((q instanceof LinkedBlockingQueue) ||
(q instanceof LinkedBlockingDeque) ||
if ((q instanceof LinkedBlockingQueue) ||
(q instanceof LinkedBlockingDeque) ||
(q instanceof ConcurrentLinkedDeque) ||
(q instanceof ConcurrentLinkedQueue)) {
testQueueIteratorRemove(q);
}

View File

@ -235,6 +235,7 @@ public class RacingCollections {
new ArrayList<Queue<Integer>>(newConcurrentDeques());
list.add(new LinkedBlockingQueue<Integer>(10));
list.add(new LinkedTransferQueue<Integer>());
list.add(new ConcurrentLinkedQueue<Integer>());
return list;
}
@ -248,6 +249,7 @@ public class RacingCollections {
private static List<Deque<Integer>> newConcurrentDeques() {
List<Deque<Integer>> list = new ArrayList<Deque<Integer>>();
list.add(new LinkedBlockingDeque<Integer>(10));
list.add(new ConcurrentLinkedDeque<Integer>());
return list;
}

View File

@ -129,6 +129,7 @@ public class ChorusLine {
deqs.add(new ArrayDeque<Integer>());
deqs.add(new LinkedList<Integer>());
deqs.add(new LinkedBlockingDeque<Integer>());
deqs.add(new ConcurrentLinkedDeque<Integer>());
equal(deqs);

View File

@ -55,6 +55,7 @@ public class ConcurrentQueueLoops {
Collection<Queue<Integer>> concurrentQueues() {
List<Queue<Integer>> queues = new ArrayList<Queue<Integer>>();
queues.add(new ConcurrentLinkedDeque<Integer>());
queues.add(new ConcurrentLinkedQueue<Integer>());
queues.add(new ArrayBlockingQueue<Integer>(items, false));
//queues.add(new ArrayBlockingQueue<Integer>(count, true));
@ -105,7 +106,7 @@ public class ConcurrentQueueLoops {
final Queue<Integer> queue;
final CyclicBarrier barrier;
int items;
Stage (Queue<Integer> q, CyclicBarrier b, int items) {
Stage(Queue<Integer> q, CyclicBarrier b, int items) {
queue = q;
barrier = b;
this.items = items;

View File

@ -40,6 +40,7 @@
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
@ -62,6 +63,7 @@ public class GCRetention {
Collection<Queue<Boolean>> queues() {
List<Queue<Boolean>> queues = new ArrayList<Queue<Boolean>>();
queues.add(new ConcurrentLinkedDeque<Boolean>());
queues.add(new ConcurrentLinkedQueue<Boolean>());
queues.add(new ArrayBlockingQueue<Boolean>(count, false));
queues.add(new ArrayBlockingQueue<Boolean>(count, true));

View File

@ -48,6 +48,7 @@ public class IteratorWeakConsistency {
test(new LinkedBlockingQueue(20));
test(new LinkedBlockingDeque());
test(new LinkedBlockingDeque(20));
test(new ConcurrentLinkedDeque());
test(new ConcurrentLinkedQueue());
test(new LinkedTransferQueue());
// Other concurrent queues (e.g. ArrayBlockingQueue) do not

View File

@ -55,6 +55,7 @@ public class OfferRemoveLoops {
testQueue(new LinkedBlockingDeque());
testQueue(new ArrayBlockingQueue(10));
testQueue(new PriorityBlockingQueue(10));
testQueue(new ConcurrentLinkedDeque());
testQueue(new ConcurrentLinkedQueue());
testQueue(new LinkedTransferQueue());
}

View File

@ -41,6 +41,7 @@
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
@ -62,6 +63,7 @@ public class RemovePollRace {
Collection<Queue<Boolean>> concurrentQueues() {
List<Queue<Boolean>> queues = new ArrayList<Queue<Boolean>>();
queues.add(new ConcurrentLinkedDeque<Boolean>());
queues.add(new ConcurrentLinkedQueue<Boolean>());
queues.add(new ArrayBlockingQueue<Boolean>(count, false));
queues.add(new ArrayBlockingQueue<Boolean>(count, true));