/* * 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.queues.intrusive; import static org.jctools.util.UnsafeAccess.UNSAFE; /** * Intrusive MPSC queue implementation based on <a href="http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue">Intrusive * MPSC node-based queue</a> as presented on <a href="http://www.1024cores.net">1024cores</a> by Dmitry Vyukov. * * @see Node */ abstract class MpscIntrusiveLinkedQueuePad0 { long p00, p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16; } abstract class MpscIntrusiveLinkedQueueProducerNodeRef extends MpscIntrusiveLinkedQueuePad0 { private final static long P_NODE_OFFSET; static { try { P_NODE_OFFSET = UNSAFE .objectFieldOffset(MpscIntrusiveLinkedQueueProducerNodeRef.class.getDeclaredField("producerNode")); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } private volatile Node producerNode; protected final Node lvProducerNode() { return producerNode; } protected final Node xchgProducerNode(Node node) { // TODO: add support for JDK < 8 per org.jctools.queues.MpscLinkedQueue / MpscLinkedQueue8 return (Node) UNSAFE.getAndSetObject(this, P_NODE_OFFSET, node); } } abstract class MpscIntrusiveLinkedQueuePad1 extends MpscIntrusiveLinkedQueueProducerNodeRef { long p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16, p17; } abstract class MpscIntrusiveLinkedQueueConsumerNodeRef extends MpscIntrusiveLinkedQueuePad1 { private final static long C_NODE_OFFSET; static { try { C_NODE_OFFSET = UNSAFE .objectFieldOffset(MpscIntrusiveLinkedQueueConsumerNodeRef.class.getDeclaredField("consumerNode")); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } private Node consumerNode; protected final Node stub = new NodeImpl(); protected final void spConsumerNode(Node node) { consumerNode = node; } protected final Node lvConsumerNode() { return (Node) UNSAFE.getObjectVolatile(this, C_NODE_OFFSET); } protected final Node lpConsumerNode() { return consumerNode; } } public class MpscIntrusiveLinkedQueue extends MpscIntrusiveLinkedQueueConsumerNodeRef { long p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16, p17; public MpscIntrusiveLinkedQueue() { super(); spConsumerNode(stub); xchgProducerNode(stub); } public boolean offer(Node node) { if (node == null) { throw new NullPointerException(); } node.setNext(null); Node prev = xchgProducerNode(node); // bubble potential prev.setNext(node); return true; } public Node poll() { Node cNode = this.lpConsumerNode(); Node next = cNode.getNext(); if (cNode == this.stub) { // consumer is stub, and next is null means queue is empty if (next == null) { return null; } // next is not null, we see a way out of stub, cNode is swapped for next and start again this.spConsumerNode(next); cNode = next; next = next.getNext(); } // cNode is not stub AND next is not null if (next != null) { this.spConsumerNode(next); // prevent GC nepotism, signal consumed to size cNode.setNext(stub); return cNode; } Node pNode = this.lvProducerNode(); // doesn't this imply a bubble? if (cNode != pNode) { return null; } offer(stub); next = cNode.getNext(); if (next != null) { this.spConsumerNode(next); // prevent GC nepotism, signal consumed to size cNode.setNext(stub); return cNode; } return null; } public Node peek() { final Node tail = this.lpConsumerNode(); if (tail == stub) { return tail.getNext(); } else { return tail; } } @SuppressWarnings("StatementWithEmptyBody") public void clear() { while (poll() != null); } /** * This is an O(n) operation as we run through all the nodes and count them.<br> * The accuracy of the value returned by this method is subject to races with producer/consumer threads. In * particular when racing with the consumer thread this method may under estimate the size.<br> * Note that passing nodes between queues, or concurrent requeuing of nodes can cause this method to return strange * values. */ public int size() { // Read consumer first, this is important because if the producer is node is 'older' than the consumer // the consumer may overtake it (consume past it) invalidating the 'snapshot' notion of size. final Node stub = this.stub; Node chaserNode = lvConsumerNode(); if (chaserNode == stub) { chaserNode = chaserNode.getNext(); } final Node producerNode = lvProducerNode(); int size = 0; // must chase the nodes all the way to the producer node, but there's no need to count beyond expected head. while (chaserNode != null && chaserNode != stub && size < Integer.MAX_VALUE) // stop at max int { if (chaserNode == producerNode) { return size + 1; } chaserNode = chaserNode.getNext(); size++; } return size; } public boolean isEmpty() { return peek() == null; } }