/**
* A Stack.
*/
public interface Stack
{
/**
* Removes the top element from this stack and returns it.
*
* @return the top element of this stack.
* @throw Exception if this stack is empty.
*/
public T pop() throws Exception;
/**
* Pushes the given element onto this stack.
*
* @param the element to be pushed onto this stack.
*/
public void push(T element);
}
In each implementation of the above interface we will exploit the class Node.
/**
* A node of a linked list.
*/
public class Node
{
private T element;
private Node next;
/**
* Initializes this node with the given element and next node.
*
* @param element the element stored in the node.
* @pre. element != null
* @param next the next node in the list.
*/
public Node(T element, Node next)
{
this.element = element;
this.next = next;
}
/**
* Returns the element of this node.
*
* @return the element of this node.
*/
public T getElement()
{
return this.element;
}
/**
* Returns the next node.
*
* @return the next node.
*/
public Node getNext()
{
return this.next;
}
/**
* Sets the next node to the given node.
*
* @param node the new next node.
*/
public void setNext(Node next)
{
this.next = next;
}
}
In the first implementation, we simply lock the whole stack when we pop or push an element.
public class First implements Stack
{
private Node top;
public First()
{
this.top = null;
}
public synchronized T pop() throws Exception
{
if (this.top == null)
{
throw new Exception();
}
else
{
T element = this.top.getElement();
this.top = this.top.getNext();
return element;
}
}
public synchronized void push(T element)
{
this.top = new Node(element, this.top);
}
}
In the second implementation, we lock the top node when we pop or push an element.
public class Second implements Stack
{
private Node top;
public Second()
{
this.top = null;
}
public T pop() throws Exception
{
Node node;
synchronized (this.top)
{
if (this.top == null)
{
throw new Exception();
}
else
{
node = this.top;
this.top = this.top.getNext();
}
}
return node.getElement();
}
public void push(T element)
{
Node node = new Node(element, null);
synchronized (this.top)
{
node.setNext(this.top);
this.top = node;
}
}
}
The third implementation does not use any locking. We exploit the class AtomicReference.
import java.util.concurrent.atomic.*;
public class Third implements Stack
{
private volatile AtomicReference> top;
public Third()
{
this.top = new AtomicReference>();
}
public T pop() throws Exception
{
Node node;
do
{
node = this.top.get();
if (node == null)
{
throw new Exception();
}
}
while (!this.top.compareAndSet(node, node.getNext()));
return node.getElement();
}
public void push(T element)
{
Node node = new Node(element, null);
do
{
node.setNext(this.top.get());
}
while (!this.top.compareAndSet(node.getNext(), node));
}
}
Also the fourth implementation does not use any locking. This time we exploit the class AtomicReferenceFieldUpdater.
import java.util.concurrent.atomic.*;
public class Fourth implements Stack
{
private volatile Node top;
private static final AtomicReferenceFieldUpdater topUpdater
= AtomicReferenceFieldUpdater.newUpdater(Fourth.class, Node.class, "top");
public Fourth()
{
this.top = null;
}
public T pop() throws Exception
{
Node node;
do
{
node = this.top;
if (node == null)
{
throw new Exception();
}
}
while (Fourth.topUpdater.compareAndSet(this, node, node.getNext()));
return node.getElement();
}
public void push(T element)
{
Node node = new Node(element, null);
do
{
node.setNext(this.top);
}
while (Fourth.topUpdater.compareAndSet(this, node.getNext(), node));
}
}