技术疑问:(a != b)与(a != (a = b))、t != (t = tail)与t != tail的区别
嘿,咱们一步一步来拆解你的问题,先从最基础的两个表达式区别说起,再结合ConcurrentLinkedQueue里的代码场景,看看为什么要这么写~
一、(a != b) 和 (a != (a = b)) 的核心区别
这俩表达式的差异,本质上是Java的表达式求值顺序加上赋值操作的副作用共同导致的:
a != b:就是单纯比较a和b当前的值(如果是对象就是引用),整个过程不会修改任何变量的值,逻辑非常直白。a != (a = b):按照Java规定的「左到右求值」规则,它的执行步骤是这样的:- 先取出左边
a的旧值(赋值操作之前的原始值); - 执行括号内的
a = b:把b的值赋值给a,同时这个赋值表达式本身的返回结果就是b的值; - 拿步骤1里的
a旧值和步骤2里的b值做不等比较。
- 先取出左边
给你举个具体例子更直观:
int a = 1, b = 2; System.out.println(a != b); // 输出true,a仍为1 System.out.println(a != (a = b)); // 实际是1和2比较,输出true,此时a已被更新为2
如果是对象引用,逻辑完全一致:
Node<Integer> a = new Node<>(1), b = new Node<>(2); System.out.println(a != b); // true,a仍指向原节点 System.out.println(a != (a = b)); // true,现在a已指向b的节点
二、t != (t = tail) 和 t != tail 的区别
回到ConcurrentLinkedQueue的那段代码,这俩表达式的差异同样来自求值顺序和赋值副作用,但放在并发场景下就有了特殊意义:
1. 表达式本身的行为差异
t != tail:仅比较t和tail当前的引用是否不同,完全不会修改t的值。t != (t = tail):执行顺序是:- 先获取
t当前的旧引用; - 把
tail的当前引用赋值给t(这是附带的副作用); - 拿步骤1的
t旧引用和步骤2里的tail引用做不等比较。
- 先获取
简单来说,这个表达式的返回结果等价于“原来的t是否不等于当前的tail”,但同时会把t更新为tail的当前值。而单独的t != tail只做比较,不会改变t的取值。
2. 并发场景下的关键作用
ConcurrentLinkedQueue是无锁的线程安全队列,这段代码用这种写法的核心原因是保证“比较旧t和当前tail”与“更新t为tail”这两个动作在同一个表达式内完成,避免中间被其他线程干扰。
如果换成分开写的形式:
boolean condition = t != tail; t = tail;
在并发环境下,这两行代码之间可能有其他线程修改了tail的值——导致condition比较的是旧的tail,但t最终被赋值的是新的tail,这就造成了逻辑不一致。而用t != (t = tail)的写法,整个比较和赋值在同一个表达式里完成,按照Java的求值规则不会被打断,确保比较的是“旧t”和“赋值瞬间的tail”,同时完成t的更新。
三、关于比较机制的疑问
其实你并没有忽略比较机制的关键——!=在对象引用比较中,始终是判断两个引用是否指向同一个对象,这一点从来没有变化。真正容易被忽略的是Java严格的左到右表达式求值顺序,以及“赋值操作本身是一个表达式,会返回值同时带来修改变量的副作用”这个特性。很多开发者可能误以为表达式的求值顺序是不确定的,但Java语言规范明确规定了操作数的求值顺序是从左到右,这也是这段代码能在并发场景下正确工作的基础。
内容的提问来源于stack exchange,提问作者Yassine Badache

