java - 这个(无锁)队列实现是线程安全的吗?

标签 java multithreading locking thread-safety

我正在尝试用Java创建一个无锁队列实现,主要用于个人学习。队列应该是通用队列,允许任意数量的读者和/或作者并发。

请您审核一下,并提出您发现的任何改进/问题吗?

谢谢。

import java.util.concurrent.atomic.AtomicReference;

public class LockFreeQueue<T> {
    private static class Node<E> {
        E value;
        volatile Node<E> next;

        Node(E value) {
            this.value = value;
        }
    }

    private AtomicReference<Node<T>> head, tail;

    public LockFreeQueue() {
        // have both head and tail point to a dummy node
        Node<T> dummyNode = new Node<T>(null);
        head = new AtomicReference<Node<T>>(dummyNode);
        tail = new AtomicReference<Node<T>>(dummyNode);
    }

    /**
     * Puts an object at the end of the queue.
     */
    public void putObject(T value) {
        Node<T> newNode = new Node<T>(value);
        Node<T> prevTailNode = tail.getAndSet(newNode);
        prevTailNode.next = newNode;
    }

    /**
     * Gets an object from the beginning of the queue. The object is removed
     * from the queue. If there are no objects in the queue, returns null.
     */
    public T getObject() {
        Node<T> headNode, valueNode;

        // move head node to the next node using atomic semantics
        // as long as next node is not null
        do {
            headNode = head.get();
            valueNode = headNode.next;
            // try until the whole loop executes pseudo-atomically
            // (i.e. unaffected by modifications done by other threads)
        } while (valueNode != null && !head.compareAndSet(headNode, valueNode));

        T value = (valueNode != null ? valueNode.value : null);

        // release the value pointed to by head, keeping the head node dummy
        if (valueNode != null)
            valueNode.value = null;

        return value;
}

最佳答案

代码不是线程安全的。考虑 putObject(...):

public void putObject(T value) {
    Node<T> newNode = new Node<T>(value);
    Node<T> prevTailNode = tail.getAndSet(newNode);
    prevTailNode.next = newNode;
}

第二个语句在设置前一个节点的 next 指针之前添加新节点。这只发生在第三个声明中。因此,有一个窗口,其中nextnull;即竞争条件。

即使您解决了这个问题,还有一个更隐蔽的问题。读取 Node 对象的 next 字段的线程不一定看到第二个线程刚刚写入的值。这是 Java 内存模型的结果。在这种情况下,确保后续读取始终看到较早写入值的方法是:

  • 声明nextvolatile,或者
  • 在同一对象上的原始互斥量中进行读取和写入。

编辑:在更详细地阅读 getObject()putObject() 的代码时,我可以看到没有什么强制 的非空值next 将被刷新到 putObject 中的内存中,并且没有任何东西强制 getObject 从主内存中读取 next。因此 getObject 代码可能会看到错误的 next 值,导致它在队列中确实有元素时返回 null

关于java - 这个(无锁)队列实现是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1634368/

相关文章:

java - 如何使用扫描仪输入逆波兰表示法计算器?

c# - 试图把我的头缠绕在线程上

多线程文本处理的成本/ yield

c# - Invoke 在底层是如何工作的?线程不是只有 1 个指令指针吗?

c# - 更新 UI 元素时锁定 Parallel Linq

c# - 如何重构这些锁?

java - Spring JPA异常翻译

java - 正方形之间的 2D 碰撞检测,简单但比 boolean 值更具体 + 不受大空间跳跃的影响

android - SQLite 连接和锁定

java - 如何将字符串变量添加到 jdbc firebird 连接上的 SQL 查询