Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/304.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 无锁队列中哪里有bug?_Java_Queue_Volatile_Lock Free - Fatal编程技术网

Java 无锁队列中哪里有bug?

Java 无锁队列中哪里有bug?,java,queue,volatile,lock-free,Java,Queue,Volatile,Lock Free,我编写了一个Java无锁队列实现。它有一个并发错误。我找不到它。这个代码并不重要。我只是担心我无法解释观察到的与易变变量相关的行为 异常(“空头”)可以看到该错误。这是不可能的状态,因为存在保持当前队列大小的原子整数。队列有一个存根元素。它规定读线程不改变尾部指针,写线程不改变头部指针 队列长度变量保证链表永远不会为空。它是一个信号灯 take方法的行为类似于获取被盗的长度值 class Node<T> { final AtomicReference<Node<T

我编写了一个Java无锁队列实现。它有一个并发错误。我找不到它。这个代码并不重要。我只是担心我无法解释观察到的与易变变量相关的行为

异常(“空头”)可以看到该错误。这是不可能的状态,因为存在保持当前队列大小的原子整数。队列有一个存根元素。它规定读线程不改变尾部指针,写线程不改变头部指针

队列长度变量保证链表永远不会为空。它是一个信号灯

take方法的行为类似于获取被盗的长度值

class Node<T> {
    final AtomicReference<Node<T>> next = new AtomicReference<Node<T>>();
    final T ref;
    Node(T ref) {
        this.ref = ref;
    }
}
public class LockFreeQueue<T> {
    private final AtomicInteger length = new AtomicInteger(1);
    private final Node stub = new Node(null);
    private final AtomicReference<Node<T>> head = new AtomicReference<Node<T>>(stub);
    private final AtomicReference<Node<T>> tail = new AtomicReference<Node<T>>(stub);

    public void add(T x) {
        addNode(new Node<T>(x));
        length.incrementAndGet();
    }

    public T takeOrNull() {
        while (true) {
            int l = length.get();
            if (l == 1) {
                return null;
            }
            if (length.compareAndSet(l, l - 1)) {
                break;
            }
        }
        while (true) {
            Node<T> r = head.get();
            if (r == null) {
                throw new IllegalStateException("null head");
            }
            if (head.compareAndSet(r, r.next.get())) {
                if (r == stub) {
                    stub.next.set(null);
                    addNode(stub);
                } else {
                    return r.ref;
                }
            }
        }
    }

    private void addNode(Node<T> n) {
        Node<T> t;
        while (true) {
            t = tail.get();
            if (tail.compareAndSet(t, n)) {
                break;    
            }
        }
        if (t.next.compareAndSet(null, n)) {
            return;
        }
        throw new IllegalStateException("bad tail next");
    }
}
类节点{
final AtomicReference next=新的AtomicReference();
最终T参考;
节点(T参考){
this.ref=ref;
}
}
公共类无锁队列{
私有最终AtomicInteger长度=新的AtomicInteger(1);
私有最终节点存根=新节点(空);
私有最终原子引用头=新原子引用(存根);
私有最终原子引用尾部=新原子引用(存根);
公共无效添加(T x){
addNode(新节点(x));
length.incrementAndGet();
}
公共T takeOrNull(){
while(true){
int l=length.get();
如果(l==1){
返回null;
}
if(长度比较数据集(l,l-1)){
打破
}
}
while(true){
节点r=head.get();
if(r==null){
抛出新的非法状态异常(“空头”);
}
if(head.compareAndSet(r,r.next.get()){
if(r==存根){
stub.next.set(null);
addNode(存根);
}否则{
返回r.ref;
}
}
}
}
私有void addNode(节点n){
节点t;
while(true){
t=tail.get();
if(尾部比较数据集(t,n)){
打破
}
}
if(t.next.compareAndSet(null,n)){
返回;
}
抛出新的非法状态异常(“下一个坏尾巴”);
}
}

我认为在takeOrNull()中使用计数器的方式有一个错误,删除存根时,长度减少1,但在最后添加存根时,不要重新增加长度,因为使用addNode()而不是add()。 假设您成功添加了一个元素,那么您的队列如下所示:

Length is 2
STUB -> FIRST_NODE -> NULL
 ^          ^
 |          |
Head       Tail
因此,现在一个线程开始执行takeOrNull(),长度减少到1,头移动到第一个_节点,由于这是存根节点,它会被重新添加到末尾,因此现在您有:

Length is 1
FIRST_NODE -> STUB -> NULL
 ^             ^
 |             |
Head          Tail
你看到了吗?长度现在是1!在下一个takeOrNull()上,您将得到NULL,即使第一个\u节点仍在队列中并且从未返回。。。您刚刚(暂时)丢失了一段数据。 此外,现在可以无限重复此操作并开始累积节点。 比如,如果你添加三个节点,长度是4,你有第一个,存根,NEW1,NEW2,NEW3。如果然后执行三个takeOrNull(),则会得到NEW2、NEW3、STUB和Length 1。 这样你就失去了元素,但我承认我不能完全确定这将如何触发异常。让我再吃一顿,再想一想

编辑:好的,食物对我有好处,我想出了一个序列来触发headnull异常。 让我们从一个包含一个元素的有效队列开始,如下所示:

Length is 2
STUB -> FIRST_NODE -> NULL
 ^          ^
 |          |
Head       Tail
现在我们有四个线程,两个线程尝试takeOrNull()和两个线程并发地add()。 两个添加线程都正确地移动了尾部指针,第一个线程将尾部从第一个移动到第二个,然后暂停。第二个add线程将then tail从第二个移动到第三个,然后更新旧tail(第二个)的下一个指针,然后递增计数器并退出。 我们只剩下:

Length is 3
STUB -> FIRST_NODE -> NULL          SECOND_NODE ->  THIRD_NODE -> NULL
 ^                                                     ^
 |                                                     |
Head                                                  Tail

现在两个takeOrNull线程被唤醒并执行,因为长度是3,所以两个线程都可以得到一个元素!第一个将磁头从存根移动到第一个,第二个将磁头从第一个移动到NULL。现在HEAD为null,下一步调用takeOrNull()时,异常

当没有使用锁定机制时,该代码如何防止数据争用?你为什么不想用锁呢?你什么时候发现问题了?您是通过一个读卡器线程获得它,还是在看到问题之前需要多个读卡器?我怀疑问题在于TakerNull的第二个while循环中有多个读卡器线程。这不是生产代码。把这当作一个练习。我测试这个队列100个读卡器和100个写卡器。