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

Java 这(无锁)队列实现是线程安全的吗?,java,multithreading,locking,thread-safety,Java,Multithreading,Locking,Thread Safety,我试图用Java创建一个无锁队列实现,主要用于个人学习。队列应为通用队列,允许任意数量的读卡器和/或写卡器同时出现 请您回顾一下,并提出您发现的任何改进/问题,好吗 多谢各位 import java.util.concurrent.atomic.AtomicReference; public class LockFreeQueue<T> { private static class Node<E> { E value; volat

我试图用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;
}
导入java.util.concurrent.AtomicReference;
公共类无锁队列{
私有静态类节点{
E值;
易失性节点;
节点(E值){
这个值=值;
}
}
私有原子参考头、尾;
public LockFreeQueue(){
//将头部和尾部都指向虚拟节点
Node dummyNode=新节点(空);
head=新原子参考(dummyNode);
tail=新原子引用(dummyNode);
}
/**
*将对象放在队列的末尾。
*/
公共对象(T值){
节点newNode=新节点(值);
节点prevTailNode=tail.getAndSet(newNode);
prevTailNode.next=newNode;
}
/**
*从队列的开头获取对象。该对象将被删除
*如果队列中没有对象,则返回null。
*/
公共T getObject(){
节点头节点、值节点;
//使用原子语义将头节点移动到下一个节点
//只要下一个节点不为空
做{
headNode=head.get();
valueNode=headNode.next;
//尝试直到整个循环以伪原子方式执行
//(即不受其他螺纹修改的影响)
}while(valueNode!=null&&!head.compareAndSet(headNode,valueNode));
T值=(valueNode!=null?valueNode.value:null);
//释放head指向的值,保持head节点为虚拟节点
if(valueNode!=null)
valueNode.value=null;
返回值;
}

<代码> > p>代码不是线程安全的。请考虑<代码> PutObjor(…)>代码>:< /P>
公共对象(T值){
节点newNode=新节点(值);
节点prevTailNode=tail.getAndSet(newNode);
prevTailNode.next=newNode;
}
第2条语句在设置前一个节点的
next
指针之前添加新节点。这只发生在第3条语句中。因此,存在一个窗口,其中
next
null
;即竞争条件

即使您解决了这个问题,也存在一个更隐蔽的问题。读取节点对象的
next
字段的线程不一定会看到第二个线程刚刚写入的值。这是Java内存模型的结果。在这种情况下,确保以下读取始终看到先前写入的值的方法是其他:

  • next
    声明为
    volatile
    ,或
  • 在同一对象上的基本互斥体中执行读写操作

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

我发现您的代码只有两个问题:

  • 一个是上面提到的内存操作排序问题(可以通过声明
    next
    value
来解决)(节点:value也有同样的问题)

  • 第二个更微妙,与并发无关:在
    getObject
    中返回一个对象后,仍然保留其头部的引用。这可能导致内存泄漏

  • 否则,算法就可以了。一个模糊的演示(假设以上是固定的):

    L1:
    tail
    永远不能从队列中删除。这是因为当某个内容存储在
    tail
    中时,它具有
    next==null
    。此外,当您将某个内容分配给
    xxx.next
    时(仅在
    putObject
    中),它不能是
    tail
    ,因为getAndSet的原子性以及易失性写入和后续读取之间的顺序-假设您读取的是非空
    tail。接下来
    ,此值必须由
    putObject
    写入,因此发生在它的最后一行之后。这意味着它发生在前一行之后,这意味着ns我们读取的值不是来自
    tail

    因此,
    putObject
    中的每个对象最终都可以从
    头部
    访问。这是因为我们在
    尾部
    后连接,只有在我们将新节点的引用写入其
    下一个
    后,才能删除此节点,这意味着可以从
    头部
    访问新节点

    添加的对象通过
    putObject
    中的
    getAndSet
    操作进行简单排序

    根据
    getObject
    中成功的
    compareAndSet
    操作对出列对象进行排序


    getObject
    /
    putObject
    是根据写入/读取volatile字段
    next

    进行排序的,如果您调用

    new LockFreeQueue<?>().getObject();
    
    new LockFreeQueue().getObject();
    

    由于您不在
    valueNode
    上执行任何空性检查,尽管上面有防范措施。

    您应该看看java.util.concurrent.ConcurrentLinkedQueue的实现
    它与您试图实现的功能基本相同

    抱歉,但是您错误地认为next为null。实际上,tail.next始终为null,并且不会产生任何问题。如果getObject中next为null,则循环终止并返回null(que)
    new LockFreeQueue<?>().getObject();