(a != b)与(a != (a = b))之间的区别是什么?

4

最近的问题中,我们发现了以下代码:

// p, t, q and tail are Node<E> objects.
p = (p != t && t != (t = tail)) ? t : q;

省略问题的背景,我对以下行为感兴趣:
t != (t = tail)

考虑到这些是相同类型的对象,无论类型如何。这和以下的情况有什么区别:

t != tail

我可能在比较机制中漏掉了什么关键的东西吗?

编辑

如果有人想知道,这是在java.utilConcurrentLinkedQueue类中找到的,位于第352行。


单个“=”是赋值运算符。这是一种将t分配给tail并检查其引用不等于tail(此时始终为false)的扭曲方式。 - spi
区别在于,第一个中t获取尾部的值,在第二个中没有。 - Vel
2
@YassineBadache 我讨厌在任何代码中看到这些结构。在核心Java中有它确实不太令人放心 :) - spi
1
好奇的人可以了解一下:从“简单”实现到ConcurrentLinkedQueue中这个“复杂”的实现步骤(!)似乎源自这个变更集:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/902486a8e414#l3.202 - Marco13
2
@Eugene他的风格和极致微优化已经引起了其他问题(如您所知)。代码的先前版本(使用continue retry;)也是我在代码审查中永远不会接受的,但这个代码库的这个区域非常特殊和微妙。我只是认为(或者可能只是希望?-至少:我必须假设),Doug Lea有深刻的技术原因来确切地做到这一点。在某些情况下(比如这种情况),我很想听听他们的意见... - Marco13
显示剩余14条评论
3个回答

3
t != (t = tail)

等同于

oldt = t;
t = tail;
... oldt != t...

即原始值ttail进行比较,同时将t赋值为tail

这是一种简写方式。

if (t != tail) {
    t = tail;
}

1
我懂得更好了。Oracle开发人员有时会用一些奇怪的方法来处理东西。我想知道他们是否有他们的理由。谢谢! - Yassine Badache
2
@AbdulAhad,我看到了你在答案中提到的内容,但如果你是正确的,那么条件将始终被评估为false(除非某个其他线程在赋值后和条件之前更改了t的值),这是不正确的。尝试运行int t = 1; int tail = 2; if (t!=(t = tail)) System.out.println("not equal");。你会看到打印出“not equal”。 - Eran
1
@Eran 嗯,看起来这并不完全是同一件事... https://stackoverflow.com/a/48322642/1059372 有时这很重要:http://jeremymanson.blogspot.md/2008/12/benign-data-races-in-java.html。但是,我还没有深入研究过OP的问题代码,所以不能非常确定。 - Eugene
2
我认为你关于“简短方式”的说法至少是误导性的(如果不是错误的)。赋值t=tail 总是发生,而不仅仅是在它们不相等的情况下。重点是条件检查是否由于该赋值而更改了值。这在你回答的第一部分中有些清晰,但...可以用适当的if替换。编辑:(关于你上面的第一条评论):我认为整个重点是检测另一个线程是否修改了“tail”。 - Marco13
1
@Marco13非常高兴不仅仅是我这样想。由于这是一个并发数据结构,当您查看它时,有人可能已经删除了尾部。据我所知,这就是此代码的作用。 - Eugene
显示剩余3条评论

3

第一段代码:

t != (t = tail)

将尾部指定给变量t,然后将t与新值进行比较。 第二种方法将会比较变量t和尾部。

-3

注意:另一个答案是Java实际正在执行的操作

synchronized(x) {
     if(t != (t = tail));
}

等同于

synchronized(x) {
    t = tail;

    if(t != t) {
        // ...
    }
}

基本上,()运算符返回被分配的内容的引用。
public class Test {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;

        if(a != (b = a)) {
            System.out.println("however, there is an issue with a != (a = b), Java bug");
        } else {
            System.out.println("assignment first, obvious by inspection");
        }
    }
}

然而,相同的代码在 C 中可以正常运行。如果我要猜测,这在 Java 中是无意的,任何从 C 上的类似事情的偏离都是愚蠢的。Oracle 可能不希望它被废弃,假设它是一个无意的 bug,这很可能是。

我下次再和他们谈话的时候会解决这个问题。我仍然对他们在与苹果公司合谋影响在国防部的 Adobe 事件中设置我母亲主页为 ask.com 感到生气。不过 Apple 公司在我必须点击超过 400 次才能重试我女儿的视频库重新下载失败后,一周内修复了 iTunes,所以这个问题只限于 Oracle。微软也受影响,因此每个人都对此感到不满。

#include <iostream>

static int ref = 0;

class t {
public:
    t(int x) : x(x), r(ref) { ref++; }
    t(const t& o) : x(o.x), r(o.r) { }
    t& operator=(const t& o) { x = o.x; r = o.r; return *this; }
    bool operator!=(const t& o) const { return r != o.r; }
private:
    int x;
    int r;
};

int main() {
    t a(1);
    t b(2);

    if(a != (a = b)) {
        std::cout <<  "assignment\n";
    } else {
        std::cout <<  "no assignment\n";
    }

    return 0;
}

1
嗯,实际上并不是这样的。在等号右侧的赋值之前,首次使用的t引用已经被计算出来了。我一开始也和你想得一样,但是这段代码证明了它是错误的:Object p=null, t="t", tail="tail", q="q"; p = (p != t && t != (t = tail)) ? t : q; System.out.println(p); // 输出 "tail" - spi
仍然没有运气。请再试一次 :) - spi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接