Skip to content

Commit

Permalink
Add a ConcurrentLinkedStack, based on WFIStack
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvest committed Jun 21, 2020
1 parent 19658a5 commit 0294412
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jctools.stacks;

import java.util.Iterator;

/**
* A concurrent, unbounded, and non-blocking stack based on linked nodes.
* Concurrent push and pop executes safely across multiple threads.
* <p>
* Unlike {@code java.util.concurrent.ConcurrentLinkedDeque}, this class only supports
* the stack APIs, and does not support internal-remove.
* On the other hand, this class has a constant time {@link #size()} method,
* and a wait-free {@link #push(Object)} method.
* <p>
* The {@link #iterator()} operates on a safely captured snapshot of the stack,
* and is <em>weakly consistent</em>. This follows the precedent of the
* {@code java.util.concurrent} package, as defined in its package summary.
* <p>
* This implementation is based on the {@link WFIStack}.
*/
public class ConcurrentLinkedStack<T> implements Iterable<T>
{
private final WFIStack<LinkedNode<T>> stack;

/**
* Create a new empty stack.
*/
public ConcurrentLinkedStack()
{
stack = new WFIStack<>();
}

/**
* Push the given object onto the stack.
*
* @param value The object to push onto the stack.
*/
public void push(T value)
{
stack.push(new LinkedNode<>(value));
}

/**
* Pop the top-most object from the stack.
*
* @return The object that was the head of the stack, or {@code null} if the stack was empty.
*/
public T pop()
{
LinkedNode<T> node = stack.pop();
return node == null ? null : node.value;
}

/**
* Peek at the top of the stack, without removing said object.
* <p>
* Note that the result may be outdated as soon as this method returns.
*
* @return The object that is the top of the stack, or {@code null} if the stack is empty.
*/
public T peek()
{
LinkedNode<T> node = stack.peek();
return node == null ? null : node.value;
}

/**
* Get the size of the stack.
*
* @return The number of objects on this stack.
*/
public int size()
{
return stack.size();
}

/**
* Tell whether the stack is empty.
*
* @return {@code true} if the stack is empty.
*/
public boolean isEmpty()
{
return stack.isEmpty();
}

@Override
public Iterator<T> iterator()
{
return new Iterator<T>()
{
private final Iterator<LinkedNode<T>> inner = stack.iterator();

@Override
public boolean hasNext()
{
return inner.hasNext();
}

@Override
public T next()
{
LinkedNode<T> next = inner.next();
return next.value;
}
};
}

private static class LinkedNode<T> extends WFIStack.Node
{
private final T value;

private LinkedNode(T value)
{
this.value = value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jctools.stacks;

import org.junit.Test;

import java.util.Iterator;

import static org.junit.Assert.*;

/**
* @see WFIStackTest most of the correctness of this implementation is derived from the WFIStack.
*/
public class ConcurrentLinkedStackTest
{
@Test
public void sanityCheckMethods()
{
ConcurrentLinkedStack<Object> stack = new ConcurrentLinkedStack<Object>();
assertTrue(stack.isEmpty());
assertEquals(0, stack.size());
assertFalse(stack.iterator().hasNext());
assertNull(stack.pop());
assertNull(stack.peek());

stack.push(1);

assertEquals(1, stack.peek());
assertFalse(stack.isEmpty());
assertEquals(1, stack.size());
Iterator<Object> iterator = stack.iterator();
assertTrue(iterator.hasNext());
assertEquals(1, iterator.next());
assertFalse(iterator.hasNext());

assertEquals(1, stack.pop());

assertTrue(stack.isEmpty());
assertEquals(0, stack.size());
assertFalse(stack.iterator().hasNext());
assertNull(stack.pop());
assertNull(stack.peek());

stack.push(2);
stack.push(3);

assertFalse(stack.isEmpty());
assertEquals(2, stack.size());
iterator = stack.iterator();
assertTrue(iterator.hasNext());
assertEquals(3, iterator.next());
assertTrue(iterator.hasNext());
assertEquals(2, iterator.next());
assertFalse(iterator.hasNext());
assertEquals(3, stack.peek());

assertEquals(3, stack.pop());

assertEquals(2, stack.peek());

assertEquals(2, stack.pop());

assertTrue(stack.isEmpty());
assertEquals(0, stack.size());
assertFalse(stack.iterator().hasNext());
assertNull(stack.pop());
assertNull(stack.peek());
}
}

0 comments on commit 0294412

Please sign in to comment.