com.github.benmanes.multiway.EliminationStack.java Source code

Java tutorial

Introduction

Here is the source code for com.github.benmanes.multiway.EliminationStack.java

Source

/*
 * Copyright 2013 Ben Manes. All Rights Reserved.
 *
 * 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 com.github.benmanes.multiway;

import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReference;

import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ForwardingIterator;
import com.google.common.collect.Lists;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

/**
 * An unbounded thread-safe stack based on linked nodes. This stack orders elements LIFO
 * (last-in-last-out). The <em>top</em> of the stack is that element that has been on the stack
 * the shortest time. New elements are inserted at and retrieved from the top of the stack. A
 * {@code EliminationStack} is an appropriate choice when many threads will exchange elements
 * through shared access to a common collection. Like most other concurrent collection
 * implementations, this class does not permit the use of {@code null} elements.
 * <p>
 * This implementation employs elimination to transfer elements between threads that are pushing
 * and popping concurrently. This technique avoids contention on the stack by attempting to cancel
 * operations if an immediate update to the stack is not successful. This approach is described in
 * <a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.156.8728">A Scalable Lock-free
 * Stack Algorithm</a>.
 * <p>
 * Iterators are <i>weakly consistent</i>, returning elements reflecting the state of the stack at
 * some point at or since the creation of the iterator. They do <em>not</em> throw {@link
 * java.util.ConcurrentModificationException}, and may proceed concurrently with other operations.
 * Elements contained in the stack 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 stacks, determining the
 * current number of elements requires a traversal of the elements, and so may report inaccurate
 * results if this collection is modified during traversal.
 *
 * @author Ben Manes (ben.manes@gmail.com)
 */
@ThreadSafe
final class EliminationStack<E> extends AbstractCollection<E> implements Serializable {

    /*
     * A Treiber's stack is represented as a singly-linked list with an atomic top reference and uses
     * compare-and-swap to modify the value atomically.
     *
     * The stack is augmented with an elimination array to minimize the top reference becoming a
     * sequential bottleneck. Elimination allows pairs of operations with reverse semantics, like
     * pushes and pops on a stack, to complete without any central coordination, and therefore
     * substantially aids scalability [1, 2, 3]. If a thread fails to update the stack's top reference
     * then it backs off to a collision arena where a location is chosen at random and it attempts to
     * coordinate with another operation that concurrently chose the same location. If a transfer is
     * not successful then the thread repeats the process until the element is added to the stack or
     * a cancellation occurs.
     *
     * This implementation borrows optimizations from {@link java.util.concurrent.Exchanger} for
     * choosing an arena location and awaiting a match [4].
     *
     * [1] A Scalable Lock-free Stack Algorithm
     * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.156.8728
     * [2] Concurrent Data Structures
     * http://www.cs.tau.ac.il/~shanir/concurrent-data-structures.pdf
     * [3] Using elimination to implement scalable and lock-free fifo queues
     * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.108.6422
     * [4] A Scalable Elimination-based Exchange Channel
     * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.59.7396
     */

    /** The number of CPUs */
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /** The number of slots in the elimination array. */
    static final int ARENA_LENGTH = ceilingNextPowerOfTwo((NCPU + 1) / 2);

    /** The mask value for indexing into the arena. */
    static int ARENA_MASK = ARENA_LENGTH - 1;

    /** The number of times to step ahead, probe, and try to match. */
    static final int LOOKAHEAD = Math.min(4, NCPU);

    /**
     * The number of times to spin (doing nothing except polling a memory location) before giving up
     * while waiting to eliminate an operation. Should be zero on uniprocessors. On multiprocessors,
     * this value should be large enough so that two threads exchanging items as fast as possible
     * block only when one of them is stalled (due to GC or preemption), but not much longer, to avoid
     * wasting CPU resources. Seen differently, this value is a little over half the number of cycles
     * of an average context switch time on most systems. The value here is approximately the average
     * of those across a range of tested systems.
     */
    static final int SPINS = (NCPU == 1) ? 0 : 2000;

    /** The number of times to spin per lookahead step */
    static final int SPINS_PER_STEP = (SPINS / LOOKAHEAD);

    /** A marker indicating that the arena slot is free. */
    static final Object FREE = null;

    /** A marker indicating that a thread is waiting in that slot to be transfered an element. */
    static final Object WAITER = new Object();

    static int ceilingNextPowerOfTwo(int x) {
        // From Hacker's Delight, Chapter 3, Harry S. Warren Jr.
        return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1));
    }

    /** The top of the stack. */
    final AtomicReference<Node<E>> top;

    /** The arena where slots can be used to perform an exchange */
    final PaddedAtomicReference<Object>[] arena;

    /** Creates a {@code EliminationStack} that is initially empty. */
    @SuppressWarnings("unchecked")
    public EliminationStack() {
        top = new PaddedAtomicReference<>();
        arena = new PaddedAtomicReference[ARENA_LENGTH];
        for (int i = 0; i < ARENA_LENGTH; i++) {
            arena[i] = new PaddedAtomicReference<Object>();
        }
    }

    /**
     * Creates a {@code EliminationStack} 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 EliminationStack(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    /**
     * Returns <tt>true</tt> if this stack contains no elements.
     *
     * @return <tt>true</tt> if this stack contains no elements
     */
    @Override
    public boolean isEmpty() {
        for (;;) {
            Node<E> node = top.get();
            if (node == null) {
                return true;
            }
            E e = node.get();
            if (e == null) {
                top.compareAndSet(node, node.next);
            } else {
                return false;
            }
        }
    }

    /**
     * Returns the number of elements in this stack.
     * <p>
     * Beware that, unlike in most collections, this method is <em>NOT</em> a constant-time
     * operation. Because of the asynchronous nature of these stacks, 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 stack
     */
    @Override
    public int size() {
        int size = 0;
        for (Node<E> node = top.get(); node != null; node = node.next) {
            if (node.get() != null) {
                size++;
            }
        }
        return size;
    }

    /** Removes all of the elements from this stack. */
    @Override
    public void clear() {
        Node<E> node;
        while ((node = top.get()) != null) {
            top.compareAndSet(node, node.next);
        }
    }

    /**
     * Returns {@code true} if this stack contains the specified element. More formally, returns
     * {@code true} if and only if this stack contains at least one element {@code e} such that
     * {@code o.equals(e)}.
     *
     * @param o object to be checked for containment in this stack
     * @return {@code true} if this stack contains the specified element
     */
    @Override
    public boolean contains(Object o) {
        checkNotNull(o);

        for (Node<E> node = top.get(); node != null; node = node.next) {
            E value = node.get();
            if (o.equals(value)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Retrieves, but does not remove, the top of the stack (in other words, the last element pushed),
     * or returns <tt>null</tt> if this stack is empty.
     *
     * @return the top of the stack or <tt>null</tt> if this stack is empty
     */
    public E peek() {
        for (;;) {
            Node<E> node = top.get();
            if (node == null) {
                return null;
            }
            E e = node.get();
            if (e == null) {
                top.compareAndSet(node, node.next);
            } else {
                return e;
            }
        }
    }

    /**
     * Removes and returns the top element or returns <tt>null</tt> if this stack is empty.
     *
     * @return the top of this stack, or <tt>null</tt> if this stack is empty
     */
    public @Nullable E pop() {
        for (;;) {
            Node<E> current = top.get();
            if (current == null) {
                return null;
            }

            // Attempt to pop from the stack, backing off to the elimination array if contended
            if ((top.get() == current) && top.compareAndSet(current, current.next)) {
                return current.get();
            }
            E e = tryReceive();
            if (e != null) {
                return e;
            }
        }
    }

    /**
     * Pushes an element onto the stack (in other words, adds an element at the top of this stack).
     *
     * @param e the element to push
     */
    public void push(E e) {
        checkNotNull(e);

        Node<E> node = new Node<E>(e);
        for (;;) {
            node.next = top.get();

            // Attempt to push to the stack, backing off to the elimination array if contended
            if ((top.get() == node.next) && top.compareAndSet(node.next, node)) {
                return;
            }
            if (tryTransfer(e)) {
                return;
            }
        }
    }

    @Override
    public boolean add(E e) {
        push(e);
        return true;
    }

    @Override
    public boolean remove(Object o) {
        checkNotNull(o);

        for (Node<E> node = top.get(); node != null; node = node.next) {
            E value = node.get();
            if (o.equals(value) && node.compareAndSet(value, null)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Iterator<E> iterator() {
        final class ReadOnlyIterator extends AbstractIterator<E> {
            Node<E> current = top.get();

            @Override
            protected E computeNext() {
                for (;;) {
                    if (current == null) {
                        return endOfData();
                    }
                    E e = current.get();
                    current = current.next;
                    if (e != null) {
                        return e;
                    }
                }
            }
        }
        ;
        return new ForwardingIterator<E>() {
            final ReadOnlyIterator delegate = new ReadOnlyIterator();

            @Override
            public void remove() {
                checkState(delegate.current != null);
                delegate.current.lazySet(null);
            }

            @Override
            protected Iterator<E> delegate() {
                return delegate;
            }
        };
    }

    /**
     * Returns a view as a last-in-first-out (Lifo) {@link Queue}. Method <tt>add</tt> is mapped to
     * <tt>push</tt>, <tt>remove</tt> is mapped to <tt>pop</tt> and so on. This view can be useful
     * when you would like to use a method requiring a <tt>Queue</tt> but you need Lifo ordering.
     *
     * @return the queue
     */
    public Queue<E> asLifoQueue() {
        return new AsLifoQueue<>(this);
    }

    /**
     * Attempts to transfer the element to a waiting consumer.
     *
     * @param e the element to try to exchange
     * @return if the element was successfully transfered
     */
    boolean tryTransfer(E e) {
        int start = startIndex();
        return scanAndTransferToWaiter(e, start) || awaitExchange(e, start);
    }

    /**
     * Scans the arena searching for a waiting consumer to exchange with.
     *
     * @param e the element to try to exchange
     * @return if the element was successfully transfered
     */
    boolean scanAndTransferToWaiter(E e, int start) {
        for (int i = 0; i < ARENA_LENGTH; i++) {
            int index = (start + i) & ARENA_MASK;
            AtomicReference<Object> slot = arena[index];
            // if some thread is waiting to receive an element then attempt to provide it
            if ((slot.get() == WAITER) && slot.compareAndSet(WAITER, e)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Waits for (by spinning) to have the element transfered to another thread. The element is
     * filled into an empty slot in the arena and spun on until it is transfered or a per-slot spin
     * limit is reached. This search and wait strategy is repeated by selecting another slot until a
     * total spin limit is reached.
     *
     * @param e the element to transfer
     * @param start the arena location to start at
     * @return if an exchange was completed successfully
     */
    boolean awaitExchange(E e, int start) {
        for (int step = 0, totalSpins = 0; (step < ARENA_LENGTH) && (totalSpins < SPINS); step++) {
            int index = (start + step) & ARENA_MASK;
            AtomicReference<Object> slot = arena[index];

            Object found = slot.get();
            if ((found == WAITER) && slot.compareAndSet(WAITER, e)) {
                return true;
            } else if ((found == FREE) && slot.compareAndSet(FREE, e)) {
                int slotSpins = 0;
                for (;;) {
                    found = slot.get();
                    if (found != e) {
                        return true;
                    } else if ((slotSpins >= SPINS_PER_STEP) && (slot.compareAndSet(e, FREE))) {
                        // failed to transfer the element; try a new slot
                        totalSpins += slotSpins;
                        break;
                    }
                    slotSpins++;
                }
            }
        }
        // failed to transfer the element; give up
        return false;
    }

    /**
     * Attempts to receive an element from a waiting provider.
     *
     * @return an element if successfully transfered or null if unsuccessful
     */
    @Nullable
    E tryReceive() {
        int start = startIndex();
        E e = scanAndMatch(start);
        return (e == null) ? awaitMatch(start) : e;
    }

    /**
     * Scans the arena searching for a waiting producer to transfer from.
     *
     * @param start the arena location to start at
     * @return an element if successfully transfered or null if unsuccessful
     */
    @Nullable
    E scanAndMatch(int start) {
        for (int i = 0; i < ARENA_LENGTH; i++) {
            int index = (start + i) & ARENA_MASK;
            AtomicReference<Object> slot = arena[index];

            // accept a transfer if an element is available
            Object found = slot.get();
            if ((found != FREE) && (found != WAITER) && slot.compareAndSet(found, FREE)) {
                @SuppressWarnings("unchecked")
                E e = (E) found;
                return e;
            }
        }
        return null;
    }

    /**
     * Waits for (by spinning) to have an element transfered from another thread. A marker is filled
     * into an empty slot in the arena and spun on until it is replaced with an element or a per-slot
     * spin limit is reached. This search and wait strategy is repeated by selecting another slot
     * until a total spin limit is reached.
     *
     * @param start the arena location to start at
     * @return an element if successfully transfered or null if unsuccessful
     */
    @Nullable
    E awaitMatch(int start) {
        for (int step = 0, totalSpins = 0; (step < ARENA_LENGTH) && (totalSpins < SPINS); step++) {
            int index = (start + step) & ARENA_MASK;
            AtomicReference<Object> slot = arena[index];
            Object found = slot.get();

            if (found == FREE) {
                if (slot.compareAndSet(FREE, WAITER)) {
                    int slotSpins = 0;
                    for (;;) {
                        found = slot.get();
                        if ((found != WAITER) && slot.compareAndSet(found, FREE)) {
                            @SuppressWarnings("unchecked")
                            E e = (E) found;
                            return e;
                        } else if ((slotSpins >= SPINS_PER_STEP) && (found == WAITER)
                                && (slot.compareAndSet(WAITER, FREE))) {
                            // failed to receive an element; try a new slot
                            totalSpins += slotSpins;
                            break;
                        }
                        slotSpins++;
                    }
                }
            } else if ((found != WAITER) && slot.compareAndSet(found, FREE)) {
                @SuppressWarnings("unchecked")
                E e = (E) found;
                return e;
            }
        }

        // failed to receive an element; give up
        return null;
    }

    /**
     * Returns the start index to begin searching the arena with. Uses a one-step FNV-1a hash code
     * (http://www.isthe.com/chongo/tech/comp/fnv/) based on the current thread's Thread.getId().
     * These hash codes have more uniform distribution properties with respect to small moduli
     * (here 1-31) than do other simple hashing functions. This technique is a simplified version
     * borrowed from {@link java.util.concurrent.Exchanger}'s hashIndex function.
     */
    static int startIndex() {
        long id = Thread.currentThread().getId();
        return (((int) (id ^ (id >>> 32))) ^ 0x811c9dc5) * 0x01000193;
    }

    /* ---------------- Serialization Support -------------- */

    static final long serialVersionUID = 1;

    Object writeReplace() {
        return new SerializationProxy<E>(this);
    }

    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("Proxy required");
    }

    /** A proxy that is serialized instead of the stack, containing only the elements. */
    static final class SerializationProxy<E> implements Serializable {
        final List<E> elements;

        SerializationProxy(EliminationStack<E> stack) {
            this.elements = new ArrayList<>(stack);
        }

        Object readResolve() {
            return new EliminationStack<>(Lists.reverse(elements));
        }

        static final long serialVersionUID = 1;
    }

    /**
     * An item on the stack. The node is mutable prior to being inserted to avoid object churn and
     * is immutable by the time it has been published to other threads.
     */
    static final class Node<E> extends AtomicReference<E> {
        private static final long serialVersionUID = 1L;

        Node<E> next;

        Node(E value) {
            super(value);
        }
    }

    /**
     * An AtomicReference with heuristic padding to lessen cache effects of this heavily CAS'ed
     * location. While the padding adds noticeable space, the improved throughput outweighs
     * using extra space.
     */
    static class PaddedAtomicReference<T> extends AtomicReference<T> {
        private static final long serialVersionUID = 1L;

        // Improve likelihood of isolation on <= 64 byte cache lines
        long q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, qa, qb, qc, qd, qe;

        PaddedAtomicReference() {
        }

        PaddedAtomicReference(T value) {
            super(value);
        }
    }

    /** A view as a last-in-first-out (Lifo) {@link Queue}. */
    static class AsLifoQueue<E> extends AbstractQueue<E> implements Queue<E>, Serializable {
        private static final long serialVersionUID = 1L;
        private final EliminationStack<E> stack;

        AsLifoQueue(EliminationStack<E> stack) {
            this.stack = stack;
        }

        @Override
        public boolean offer(E e) {
            return add(e);
        }

        @Override
        public E poll() {
            return stack.pop();
        }

        @Override
        public E peek() {
            return stack.peek();
        }

        @Override
        public void clear() {
            stack.clear();
        }

        @Override
        public int size() {
            return stack.size();
        }

        @Override
        public boolean isEmpty() {
            return stack.isEmpty();
        }

        @Override
        public boolean contains(Object o) {
            return stack.contains(o);
        }

        @Override
        public boolean remove(Object o) {
            return stack.remove(o);
        }

        @Override
        public Iterator<E> iterator() {
            return stack.iterator();
        }
    }
}