/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain. Use, modify, and
* redistribute this code in any way without acknowledgement.
*/
package java.util.concurrent;
import java.util.*;
import java.util.concurrent.atomic.*;
/**
* A stack based on a linked list using atomic operations for adding
* (pushing) and removing (popping) elements. The add
* method performs a push, remove performs a
* pop. Other methods similarly map to stack-based
* last-in-first-out (LIFO) operations.
*
* In addition to its use as a stack, this class is useful as an
* efficient concurrently accessible "bag", a collection to hold and
* traverse items across many threads in which you do not care about
* ordering.
*
*
This collection does not support the use of null as
* elements.
*
*
Beware that, unlike in most collections, the size
* method is NOT a constant-time operation. Because of the
* asynchronous nature of these stacks, determining the current number
* of elements requires an O(n) traversal.
* @since 1.5
* @author Doug Lea
**/
public class ConcurrentLinkedStack extends AbstractQueue
implements Queue, java.io.Serializable {
/*
* The basic strategy here relies on a classic linked-list stack,
* using CAS to relink head on push and pop. The implementation is
* a little more complicated than this though because the class
* must support of arbitrary deletion (remove(Object x)).
* Rather than just lazily nulling out nodes, and skipping
* them when they reach top, we detect and relink around nodes
* as they are removed. This is still partially lazy though.
* Multiple adjacent concurrent relinks may leave nulls in place,
* so all traversals must detect and relink lingering nulls.
*/
/** Head of the linked list */
private transient volatile AtomicLinkedNode head;
private static final AtomicReferenceFieldUpdater headUpdater = new AtomicReferenceFieldUpdater(new ConcurrentLinkedStack[0], new AtomicLinkedNode[0], "head");
private boolean casHead(AtomicLinkedNode cmp, AtomicLinkedNode val) {
return headUpdater.compareAndSet(this, cmp, val);
}
/**
* Creates an initially empty ConcurrentLinkedStack.
*/
public ConcurrentLinkedStack() {
}
/**
* Creates a ConcurrentLinkedStack initially holding the elements
* of the given collection. The elements are added in
* iterator traversal order.
*
* @param initialElements the collections whose elements are to be added.
*/
public ConcurrentLinkedStack(Collection initialElements) {
for (Iterator it = initialElements.iterator(); it.hasNext();)
add(it.next());
}
/**
* Relink over a deleted item; return next node. This is called
* whenever a null item field is encountered during any traversal.
* This is necessary (although rare) because a previous remove()
* could have linked one node to another node that was also in the
* process of being removed. Also, iterator.remove exploits the fact
* that nulls are cleaned out later to allow fully lazy deletion
* that would otherwise be O(n).
* @param deleted the deleted node. Precondition: deleted.item==null.
* @param previous the node before deleted, or null if deleted is first node
* @return the next node after previous, or first node if no previous.
**/
private AtomicLinkedNode skip(AtomicLinkedNode previous, AtomicLinkedNode deleted) {
if (previous == null) {
casHead(deleted, deleted.getNext());
return head.getNext();
}
else {
previous.casNext(deleted, deleted.getNext());
return previous.getNext();
}
}
/**
* Pushes the given element on the stack.
* @param x the element to insert
* @return true -- (as per the general contract of Queue.offer).
* @throws NullPointerException if x is null
**/
public boolean offer(E x) {
if (x == null) throw new NullPointerException();
AtomicLinkedNode p = new AtomicLinkedNode(x);
for (;;) {
AtomicLinkedNode h = head;
p.setNext(h);
if (casHead(h, p))
return true;
}
}
/**
* Returns the top element of this stack, or null if empty.
* @return the top element of this stack, or null if empty.
**/
public E peek() {
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else
return (E)item;
}
return null;
}
/**
* Removes and returns the top element of this stack, or null if empty.
* @return the top element of this stack, or null if empty.
**/
public E poll() {
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else {
p.setItem(null);
skip(previous, p);
return (E)item;
}
}
return null;
}
public boolean isEmpty() {
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else
return false;
}
return true;
}
/**
* Returns the number of elements in this collection.
*
* Beware that, unlike in most collection, this method> is
* NOT a constant-time operation. Because of the
* asynchronous nature of these queues, determining the current
* number of elements requires an O(n) traversal.
* @return the number of elements in this collection
*/
public int size() {
int count = 0;
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else {
++count;
previous = p;
p = p.getNext();
}
}
return count;
}
public boolean remove(Object x) {
/*
* Algorithm:
* 1. Find node
* Clean up after any previous removes while doing so.
* 2. Null out item field
* This will be noticed by other traversals, that will ignore
* it and/or help remove it.
* 3. Relink previous node to next node.
* If the next node is also in the process of being removed,
* this may leave a nulled node in the list. We clean this up
* during any traversal.
*/
if (x == null) return false; // nulls never present
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else if (x.equals(item) && p.casItem(item, null)) {
skip(previous, p);
return true;
}
else {
previous = p;
p = p.getNext();
}
}
return false;
}
public boolean contains(Object x) {
if (x == null) return false; // nulls never present
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else if (x.equals(item))
return true;
else {
previous = p;
p = p.getNext();
}
}
return false;
}
public Object[] toArray() {
// Use ArrayList to deal with resizing.
ArrayList al = new ArrayList();
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else {
al.add(item);
previous = p;
p = p.getNext();
}
}
return al.toArray();
}
public T[] toArray(T[] a) {
// try to use sent-in array
int k = 0;
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null && k < a.length) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else {
a[k++] = (T)item;
previous = p;
p = p.getNext();
}
}
if (p == null) {
if (k < a.length)
a[k] = null;
return a;
}
// If won't fit, use ArrayList version
ArrayList al = new ArrayList();
previous = null;
p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else {
al.add(item);
previous = p;
p = p.getNext();
}
}
return (T[])al.toArray(a);
}
public Iterator iterator() { return new Itr(); }
private class Itr implements Iterator {
/**
* Next node to return item for.
*/
private AtomicLinkedNode nextNode;
/**
* We need to hold on to item fields here because once we claim
* that an element exists in hasNext(), we must return it in the
* following next() call even if it was in the process of being
* removed when hasNext() was called.
**/
private E nextItem;
/**
* Node of the last returned item, to support remove.
*/
private AtomicLinkedNode lastRet;
/**
* Move to next valid node.
* Return item to return for next(), or null if no such.
*/
private E advance() {
lastRet = nextNode;
E x = nextItem;
AtomicLinkedNode p = (nextNode == null) ? head : nextNode.getNext();
for (;;) {
if (p == null) {
nextNode = null;
nextItem = null;
return x;
}
Object item = p.getItem();
if (item == null)
p = skip(nextNode, p);
else {
nextNode = p;
nextItem = (E)item;
return x;
}
}
}
Itr() {
advance();
}
public boolean hasNext() {
return nextNode != null;
}
public E next() {
if (nextNode == null) throw new NoSuchElementException();
return advance();
}
public void remove() {
AtomicLinkedNode l = lastRet;
if (l == null) throw new IllegalStateException();
// rely on a future traversal to relink.
l.setItem(null);
lastRet = null;
}
}
/**
* Save the state to a stream (that is, serialize it).
*
* @serialData All of the elements (each an E) in
* the proper order, followed by a null
* @param s the stream
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden stuff
s.defaultWriteObject();
AtomicLinkedNode previous = null;
AtomicLinkedNode p = head;
while (p != null) {
Object item = p.getItem();
if (item == null)
p = skip(previous, p);
else {
s.writeObject(item);
previous = p;
p = p.getNext();
}
}
// Use trailing null as sentinel
s.writeObject(null);
}
/**
* Reconstitute the Queue instance from a stream (that is,
* deserialize 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();
// Read in all elements into array and then insert in reverse order.
ArrayList al = new ArrayList();
for (;;) {
E item = (E)s.readObject();
if (item == null)
break;
al.add(item);
}
ListIterator it = al.listIterator(al.size());
while (it.hasPrevious())
add(it.previous());
}
}