com.mastfrog.util.thread.AtomicLinkedQueue Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright 2017 Tim Boudreau.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.mastfrog.util.thread;
import com.mastfrog.util.preconditions.Checks;
import static com.mastfrog.util.preconditions.Checks.notNull;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Spliterator;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* A non-blocking, thread-safe, memory-efficient queue using a simple linked
* list structure and atomic add and drain operations. Atomicity is achieved by
* using a singly-tail-linked data structure using atomic references. Mutation
* operations that affect the tail, such as add()
and
* pop()
are guaranteed to be thread-safe and non-blocking;
* operations that affect other parts of the queue are not (though
* removeByIdentity()
will tell you if it failed).
*
* Note that iteration occurs in reverse order. Identity-based removal
* operations exist; under concurrency they may spuriously fail, but will report
* that to the caller with their result value, and the caller may retry.
*
* @author Tim Boudreau
* @deprecated Moved to a more appropriate place, com.mastfrog.util.collections.AtomicLinkedQueue
.
* This version will eventually be removed.
*/
@Deprecated
public final class AtomicLinkedQueue implements Iterable {
// A basic linked list structure, where the head is found by
// iterating backwards
private final AtomicReference> tail;
private Runnable onAdd;
/**
* Create the a queue and add the first element.
*
* @param message The first element
*/
public AtomicLinkedQueue(Message message) {
tail = new AtomicReference<>(new MessageEntry<>(null, notNull("message", message)));
}
public AtomicLinkedQueue(Iterable it) {
tail = new AtomicReference<>();
for (Message m : notNull("it", it)) {
add(m);
}
}
public AtomicLinkedQueue(AtomicLinkedQueue q) {
tail = copyRef(q.tail);
}
private static AtomicReference> copyRef(AtomicReference> ref) {
MessageEntry orig = ref.get();
if (orig == null) {
return new AtomicReference<>();
} else {
return new AtomicReference<>(orig.copy());
}
}
/**
* Create a new queue.
*/
public AtomicLinkedQueue() {
tail = new AtomicReference<>();
}
public AtomicLinkedQueue copy() {
return new AtomicLinkedQueue<>(this);
}
/**
* Reverse the contents of this queue in-place. This method is not
* guaranteed to be unaffected by operations that modify the queue in other
* threads.
*/
public void reverseInPlace() {
tail.updateAndGet((MessageEntry t) -> {
if (t == null) {
return t;
}
MessageEntry head = new MessageEntry<>(null, t.message);
t = t.getPrev();
while (t != null) {
head = new MessageEntry<>(head, t.message);
t = t.getPrev();
}
return head;
});
}
/**
* Add an element to the tail of the queue
*
* @param message
* @return this
*/
public AtomicLinkedQueue add(final Message message) {
tail.getAndUpdate(new Applier<>(notNull("message", message)));
if (onAdd != null) {
onAdd.run();
}
return this;
}
/**
* Provide a runnable to invoke on every add. Important: - this is
* a mechanism for you to schedule something to be done with the
* queue - schedule a job or notify a backgroun thread. Do not do real work
* here, and especially do not call anything that synchronizes or
* blocks. The idea here is simply to provide a more flexible mechanism
* than a thread latch or similar can offer so as not to assume that waking
* up a thread is the only possible thing one might want.
*
* @param run A runnable
* @return this
*/
public AtomicLinkedQueue onAdd(Runnable run) {
this.onAdd = run;
return this;
}
/**
* Convenience method to trigger a OneThreadLatch on add.
*
* @param latch The latch
* @return this
*/
public AtomicLinkedQueue onAdd(OneThreadLatch latch) {
this.onAdd = latch::releaseOne;
return this;
}
private static class Applier implements UnaryOperator> {
private final Message message;
public Applier(Message message) {
this.message = message;
}
@Override
public MessageEntry apply(MessageEntry t) {
if (t == null) {
return new MessageEntry<>(null, message);
} else {
return new MessageEntry<>(t, message);
}
}
}
/**
* Drain the queue, returning a list, tail-first.
*
* @return A list of messages
*/
public List drain() {
MessageEntry oldTail = tail.getAndSet(null);
// Populate the list iterating backwards from the tail
if (oldTail != null) {
if (oldTail.getPrev() == null) {
return Collections.singletonList(oldTail.message);
}
List all = new LinkedList<>();
oldTail.drainTo(all);
return all;
}
return Collections.emptyList();
}
/**
* Drain the queue to an existing list. Note that the queue will be drained
* to index 0 in the list - if it has existing contents, those will be
* pushed forward (for best performance, pass in a LinkedList).
*
* @param all The list
* @return the list
*/
public List drainTo(List all) {
MessageEntry oldTail = tail.getAndSet(null);
// Populate the list iterating backwards from the tail
if (oldTail != null) {
if (oldTail.getPrev() == null) {
all.add(oldTail.message);
return all;
}
oldTail.drainTo(all);
}
return all;
}
/**
* Drain the queue, passing a visitor to visit each element (in reverse
* order) as it is drained.
*
* @param visitor A visitor
* @return A list of elements
*/
public List drain(Consumer visitor) {
// Do the minimal amount under the lock
MessageEntry oldTail = tail.getAndSet(null);
// Populate the list iterating backwards from the tail
if (oldTail != null) {
if (oldTail.getPrev() == null) {
visitor.accept(oldTail.message);
return Collections.singletonList(oldTail.message);
}
List all = new LinkedList<>();
oldTail.drainTo(all, visitor);
return all;
}
return Collections.emptyList();
}
/**
* Returns an iterator in reverse order of the queue contents at the
* time the iterator was created.
*
* @return An iterator
*/
@Override
public Iterator iterator() {
MessageEntry tailLocal = this.tail.get();
if (tailLocal == null) {
return Collections.emptyIterator();
}
return new It<>(tailLocal);
}
/**
* Return a list containing the contents of this queue in order.
*
* @return A list
*/
public List asList() {
List result = new LinkedList<>();
MessageEntry t = tail.get();
while (t != null) {
result.add(0, t.message);
t = t.getPrev();
}
return result;
}
/**
* Clear the contents of this queue.
*/
public void clear() {
tail.set(null);
}
/**
* Divide the contents of this queue into two others based on matching the
* passed predicate, and clear this queue.
*
* @param pred A predicate
* @param c A consumer which will be called with two queues, accepted and
* rejected in that argument order
*/
public void filterAndDrain(Predicate pred, BiConsumer, AtomicLinkedQueue> c) {
filter(tail.getAndSet(null), pred, c);
}
/**
* Divide the contents of this queue into two others based on matching the
* passed predicate, not modifying.
*
* @param pred A predicate
* @param c A consumer which will be called with two queues, accepted and
* rejected in that argument order
*/
public void filter(Predicate pred, BiConsumer, AtomicLinkedQueue> c) {
filter(tail.get(), pred, c);
}
private void filter(MessageEntry t, Predicate pred, BiConsumer, AtomicLinkedQueue> c) {
AtomicLinkedQueue accepted = new AtomicLinkedQueue<>();
AtomicLinkedQueue rejected = new AtomicLinkedQueue<>();
while (t != null) {
if (pred.test(t.message)) {
accepted.add(t.message);
} else {
rejected.add(t.message);
}
t = t.getPrev();
}
accepted.reverseInPlace();
rejected.reverseInPlace();
c.accept(accepted, rejected);
}
static class It implements Iterator {
private MessageEntry en;
public It(MessageEntry en) {
this.en = en;
}
@Override
public boolean hasNext() {
return en != null;
}
@Override
public T next() {
T result = en.message;
en = en.getPrev();
return result;
}
}
/**
* Determine if the queue is empty.
*
* @return true if it is empty
*/
public boolean isEmpty() {
return tail.get() == null;
}
/**
* Get the current size
*
* @return The size
*/
public int size() {
MessageEntry tailLocal = this.tail.get();
int result = 0;
while (tailLocal != null) {
result++;
tailLocal = tailLocal.getPrev();
}
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
MessageEntry tailLocal = this.tail.get();
while (tailLocal != null) {
sb.insert(0, tailLocal.message);
if (tailLocal.getPrev() != null) {
sb.insert(0, ",");
}
tailLocal = tailLocal.getPrev();
}
return sb.toString();
}
/**
* Pop the most recently added element from this queue.
*
* @return A message or nulk
*/
@SuppressWarnings("unchecked")
public Message pop() {
Object[] m = new Object[1];
tail.getAndUpdate(msg -> {
if (msg == null) {
return null;
}
m[0] = msg.message;
return msg.getPrev();
});
return (Message) m[0];
}
/**
* Get the message at the specified index; note that since this is a
* singly-linked atomic queue, this is not a constant-time operation, and
* the queue's contents may be changed by another thread while it is
* proceeding; in particular, do not use if you expect the queue to get
* smaller while in a call to get().
*
* @param index The offset from the tail
* @throws NoSuchElementException if the index > size()
* @throws IllegalArgumentException if the index is negative
* @return A message
*/
public Message get(int index) {
if (index < 0) {
throw new IllegalArgumentException("Negative index");
}
int count = 0;
MessageEntry t = tail.get();
while (count < index) {
t = t.prev;
count++;
if (t == null) {
throw new NoSuchElementException("No element " + index);
}
}
return t.message;
}
/**
* Atomically replace the contents of this queue with the passed collection;
* note that this will be done in reverse-iteration-order, so the top
* message will be the last one in the passed collection.
*
* @param newContents The new contents
* @throws IllegalArgumentException if a null is encountered in the passed
* collection
* @return this
*/
public AtomicLinkedQueue replaceContents(Iterable extends Message> newContents) {
MessageEntry curr = null;
for (Message m : notNull("newContents", newContents)) {
if (m == null) {
throw new IllegalArgumentException("newContents collection contains nulls");
}
curr = new MessageEntry<>(curr, m);
}
tail.set(curr);
return this;
}
/**
* Pop the tail of this queue and push it into another queue.
*
* @param other The other queue
* @param ifNull Will supply a value if this queue is empty
* @return The maessage which was moved
*/
@SuppressWarnings("unchecked")
public Message popInto(AtomicLinkedQueue other, Supplier ifNull) {
Checks.notSame("this", "other", this, other);
Object[] result = new Object[1];
tail.getAndUpdate(oldTail -> {
if (oldTail != null) {
MessageEntry prev = oldTail.getPrev();
other.tail.getAndUpdate(oldOtherTail -> {
oldTail.updatePrev(oldPrev -> {
result[0] = oldTail.message;
return oldOtherTail;
});
result[0] = oldTail.message;
return oldTail;
});
return prev;
} else {
other.tail.getAndUpdate(otherOldTail -> {
Message msg;
result[0] = msg = ifNull.get();
return new MessageEntry<>(otherOldTail, msg);
});
return null;
}
});
return (Message) result[0];
}
/**
* Pop the tail of this queue, removing it, and using the passed supplier if
* this queue was empty.
*
* @param ifNone A supplier for values when none is available to pop
* @return The popped message or the result from the supplier
*/
public Message pop(Supplier ifNone) {
Message result = pop();
return result == null ? ifNone.get() : result;
}
/**
* Remove an object, using identity (not equality) testing.
* Behavior under concurrent access: This method may return false
* because another thread is part-way through removing a node adjacent to
* one that exists and does contain the requested object - making it
* apparently not present when the linked list of nodes is traversed. So
* removal may
* spuriously fail, but the return value will reliably indicate that.
*
* @param msg An element to remove
* @return True if it is removed
*/
public boolean removeByIdentity(Message msg) {
Remover remover = new Remover<>(msg);
tail.updateAndGet(remover);
if (!remover.removed) {
tail.updateAndGet(remover);
}
return remover.removed;
}
boolean contains(Message msg) {
MessageEntry top = tail.get();
while (top != null) {
if (msg == top.message) {
return true;
}
top = top.getPrev();
}
return false;
}
/**
* Create a concurrent, parallelizable Spliterator over this queue; the
* stream's contents will reflect the contents of this queue at the time or
* some time after its creation (calling the spliterator's split methods
* create a copy of some portion of the original data).
*
* @return A stream
*/
@Override
public Spliterator spliterator() {
return new QSplit<>(tail.get());
}
/**
* Create a stream over this queue; the stream's contents will reflect the
* contents of this queue at the time or some time after its creation
* (calling the spliterator's split methods create a copy of some portion of
* the original data).
*
* @return A stream
*/
public Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* Create a stream over this queue; the stream's contents will reflect the
* contents of this queue at the time or some time after its creation
* (calling the spliterator's split methods create a copy of some portion of
* the original data).
*
* @return A stream
*/
public Stream parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
static final class Remover implements UnaryOperator> {
private final Message toRemove;
boolean removed;
public Remover(Message toRemove) {
this.toRemove = toRemove;
}
@Override
public MessageEntry apply(MessageEntry t) {
if (t == null) {
return null;
}
MessageEntry prev = t.prev;
if (t.message == toRemove) {
removed = true;
return prev;
} else {
t.updatePrev(this);
}
return t;
}
}
/**
* A single linked list entry in the queue
*/
static final class MessageEntry {
private volatile MessageEntry prev;
final Message message;
@SuppressWarnings("rawtype")
private static final AtomicReferenceFieldUpdater UPDATER
= AtomicReferenceFieldUpdater.newUpdater(MessageEntry.class, MessageEntry.class, "prev");
MessageEntry(MessageEntry prev, Message message) {
this.prev = prev;
this.message = message;
}
@Override
public String toString() {
return Objects.toString(message);
}
@SuppressWarnings("unchecked")
MessageEntry getPrev() {
return (MessageEntry) UPDATER.get(this);
}
MessageEntry copy() {
MessageEntry p = getPrev();
if (p == this) {
throw new IllegalStateException("Loop on " + message);
}
// If we do this via recursion, the stack can overflow
MessageEntry result = new MessageEntry<>(null, message);
MessageEntry curr = result;
while (p != null) {
MessageEntry newPrev = new MessageEntry<>(null, p.message);
curr.prev = newPrev;
curr = newPrev;
p = p.getPrev();
}
return result;
}
@SuppressWarnings("unchecked")
void setPrev(MessageEntry prev) {
if (prev == this) {
throw new IllegalArgumentException("Previous cannot be self");
}
UPDATER.set(this, prev);
}
@SuppressWarnings("unchecked")
void updatePrev(UnaryOperator> uo) {
UPDATER.updateAndGet(this, uo);
if (prev == this) {
prev = null;
throw new IllegalArgumentException("Previous cannot be self");
}
}
@SuppressWarnings("unchecked")
MessageEntry replacePrev(UnaryOperator u) {
MessageEntry result = (MessageEntry) UPDATER.getAndUpdate(this, u);
if (prev == this) {
prev = null;
throw new IllegalArgumentException("Previous cannot be self");
}
return result;
}
@SuppressWarnings("unchecked")
boolean updatePrev(MessageEntry expect, MessageEntry nue) {
return UPDATER.compareAndSet(this, expect, nue);
}
void drainTo(List super Message> messages) {
// Iterate backwards populating the list of messages,
// and also collect the total byte count
MessageEntry e = this;
while (e != null) {
messages.add(0, e.message);
e = e.getPrev();
}
}
void drainTo(List super Message> messages, Consumer visitor) {
// Iterate backwards populating the list of messages,
// and also collect the total byte count
MessageEntry e = this;
while (e != null) {
visitor.accept(e.message);
messages.add(0, e.message);
e = e.getPrev();
}
}
}
static class QSplit implements Spliterator {
private final AtomicReference> curr = new AtomicReference<>();
public QSplit(MessageEntry tail) {
curr.set(tail);
}
@Override
public boolean tryAdvance(Consumer super T> action) {
return curr.getAndUpdate(old -> {
if (old != null) {
action.accept(old.message);
return old.prev;
}
return null;
}) != null;
}
// This needs to be synchronized so that
// we can't split while another split is in
// progress and wind up with two splits with
// the same elements, since we touch curr twice.
@Override
public synchronized Spliterator trySplit() {
// Get the current tail
MessageEntry origTail = curr.get();
if (origTail == null) {
return null;
}
// We will be copying all entries - at this point we are
// immune from any changes made in the original queue
MessageEntry copy = origTail.copy();
long sz = size(copy);
if (sz < 2) {
return null;
}
long halfwayPoint = sz / 2;
long count = 0;
MessageEntry hp = copy;
while (count < halfwayPoint) {
hp = hp.getPrev();
if (hp == null) {
return null;
}
count++;
}
// This detaches the tail end of our copy below the
// halfway point from this one
MessageEntry backHalf = hp.replacePrev(old -> {
return null;
});
if (backHalf == null) {
return null;
}
curr.set(copy);
return new QSplit<>(backHalf);
}
private long size(MessageEntry e) {
long result = 0;
while (e != null) {
e = e.getPrev();
result++;
}
return result;
}
@Override
public long estimateSize() {
return size(curr.get());
}
@Override
public int characteristics() {
return Spliterator.CONCURRENT | Spliterator.NONNULL
| Spliterator.ORDERED | Spliterator.SIZED;
}
}
}