具有批量定向drainAll的非阻塞队列

时间:2016-05-24 13:42:18

标签: java multithreading queue nonblocking

我们有一个专家,多生产者(用户)和单一消费者(Engine)队列。用户线程运行频率更高,并始终将单个元素添加到队列中。 Engine线程操作运行频率较低,并批量处理堆栈元素。如果堆栈为空,它将停放,直到用户线程添加了一个条目。这样,只有当队列从空变为1时才需要进行通知。

在这个实现中,不是Engine线程迭代并一次删除一个项目,而是将它们全部删除 - drainAll,而不是drainTo。没有其他操作可以改变堆栈 - 只是用户线程添加,以及引擎线程drainAll。

目前我们通过同步链表执行此操作,我们想知道是否有非阻塞方式来执行此操作。对JDK类的drainTo操作将迭代堆栈,我们只想在一次操作中获取堆栈中的所有内容,而不进行迭代 - 因为每次迭代都会遇到volatile / cas相关逻辑,所以我们理想情况下只需要点击一次,每个排水管。引擎线程可以迭代并操作每个单独的元素,而无需触及sync / volatile / cas操作。

当前的实现类似于:

public class SynchronizedPropagationQueue implements PropagatioQueue {
    protected volatile PropagationEntry head;
    protected volatile PropagationEntry tail;

    protected synchronized void addEntry( PropagationEntry entry ) {
        if ( head == null ) {
            head = entry;
            notifyWaitOnRest();
        } else {
            tail.setNext( entry );
        }
        tail = entry;
    }

    @Override
    public synchronized PropagationEntry drainAll() {
        PropagationEntry currentHead = head;
        head = null;
        tail = null;
        return currentHead;
    }

    public synchronized void waitOnRest() {
        try {
            log.debug("Engine wait");
            wait();
        } catch (InterruptedException e) {
            // do nothing
        }
        log.debug("Engine resumed");
    }


    @Override
    public synchronized void notifyWaitOnRest() {
        notifyAll();
    }
}

ASDF

2 个答案:

答案 0 :(得分:0)

Stacks有一个非常简单的非阻塞实现,支持并发" pop all"操作容易,并且可以容易地检测到空的>非空转换。您可以让所有生产者将项目推送到堆栈,然后让引擎立即清空整个项目。它看起来像这样:

public class EngineQueue<T>
{
    private final AtomicReference<Node<T>> m_lastItem = new AtomicReference<>();

    public void add(T item)
    {
        Node<T> newNode = new Node<T>(item);
        do {
            newNode.m_next = m_lastItem.get();
        } while(!m_lastItem.compareAndSet(newNode.m_next, newNode)); 

        if (newNode.m_next == null)
        {
            // ... just went non-empty signal any waiting consumer
        }
    }

    public List<T> removeAll()
    {
        Node<T> stack = m_lastItem.getAndSet(null);
        // ... wait for non-empty if necessary 
        List<T> ret = new ArrayList<>();
        for (;stack != null; stack=stack.m_next)
        {
            ret.add(stack.m_data);
        }
        Collections.reverse(ret);
        return ret;
    }
    private static class Node<U>
    {
        Node<U> m_next;
        final U m_data;
        Node(U data)
        {
            super();
            m_data = data;
        }
    }
}

对于空的信号 - &gt;非空转换,可以使用正常同步。如果你只是在检测到空状态时这样做,那就不会很昂贵......因为你只有在你失去工作时才能进入空状态。

答案 1 :(得分:0)

  

目前我们通过同步链表执行此操作,我们想知道是否有非阻塞方式来执行此操作。 JDK类上的drainTo操作将迭代堆栈,我们只想在一次操作中获取堆栈中的所有内容,而不进行迭代

也许我不明白,但似乎使用BlockingQueue.drainTo(...)方法会比您的实施更好。例如,LinkedBlockingQueue.drainTo(...)方法只围绕该方法锁定一次 - 我看到没有迭代开销。

如果这不是学术讨论,那么我怀疑你的性能问题与队列本身有关,并将你的工作集中在其他方面。如果它是学术性的,那么@Matt的答案可能会更好,尽管肯定会有更多代码要编写以支持完整的Collection方法列表。