amiwrong123
V2EX  ›  Java

我现在连个三目表达式都看不懂了…

By amiwrong123 at 2020 年 7 月 20 日 · 10786 次点击

ConcurrentLinkedQueue 的 offer 方法有个奇怪的三目表达式

*/
    public boolean offer(E e) {
        checkNotNull(e);
        final Node<E> newNode = new Node<E>(e);

        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            if (q == null) {
                // p is last node
                if (p.casNext(null, newNode)) {
                    // Successful CAS is the linearization point
                    // for e to become an element of this queue,
                    // and for newNode to become "live".
                    if (p != t) // hop two nodes at a time
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
            else if (p == q)
                // We have fallen off list.  If tail is unchanged, it
                // will also be off-list, in which case we need to
                // jump to head, from which all live nodes are always
                // reachable.  Else the new tail is a better bet.
                p = (t != (t = tail)) ? t : head;
            else
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

这个三目表达式(t != (t = tail)) ? t : head;的左边看起来很奇怪,(t != (t = tail))本质上不就是(t != t)吗,那这不是肯定不成立吗?我佛了

jdk8

58 条回复  •  2020-08-21 17:00:28 +08:00
TtTtTtT
   1
TtTtTtT  
   2020 年 7 月 20 日   ❤️ 1
=。=本质理解错了呀。。
x = (t != (t = tail))
是
newT = tail
x = t != newT
t = newT
amiwrong123
   2
amiwrong123  
OP
   2020 年 7 月 20 日 via Android
@TtTtTtT
你意思这个执行过程是从左到右,前一秒等号左右两边都是旧 t,后一秒等号左边是旧 t 右边是新 t

是这样的吗😂
zmj1316
   3
zmj1316  
   2020 年 7 月 20 日 via Android
取值有顺序的,左边的 t 早于右边的赋值吧
no1xsyzy
   4
no1xsyzy  
   2020 年 7 月 20 日
话说这也不是三目看不懂啊……
chairuosen
   5
chairuosen  
   2020 年 7 月 20 日   ❤️ 3
谁这么写代码我打死他,相当于 get 里干 set 的活
TtTtTtT
   6
TtTtTtT  
   2020 年 7 月 20 日   ❤️ 4
@amiwrong123 你说的我完全没弄懂。。
我给你简单解释一下:
t = tail 是用来获取当前最新的 tail 。
(t != (t = tail)) 就是检查一下 t 是不是最新的 tail,同时将 t 更新为最新的 tail 。

在 Java 中,赋值语句返回所赋的值,比如 a = 1 返回 a 。

@chairuosen JDK 里这种写法很多,这样编译的时候会减少 slots 的占用。
TtTtTtT
   7
TtTtTtT  
   2020 年 7 月 20 日
@amiwrong123 写错了,a = 1 返回 1 。
hangs
   8
hangs  
   2020 年 7 月 20 日   ❤️ 1
这里有个说明,我觉得说的很明白了

https://segmentfault.com/q/1010000022097461/a-1020000022098182

我理解一个是赋值表达式本身是有返回的,返回值正如 @TtTtTtT 所说,是被赋值的值,另一个是开头的 t,其实读的还是旧值
chairuosen
   9
chairuosen  
   2020 年 7 月 20 日
@TtTtTtT #6 写代码第一给人看,第二才是给机器看。如果是底层万年不变的可以用写奇技淫巧,业务代码这么写被喷成狗
amiwrong123
   10
amiwrong123  
OP
   2020 年 7 月 20 日 via Android
@TtTtTtT
赋值语句我懂了,而且这个比较运算之前我好像理解错了。

(t != (t = tail)) 实际上是左边的 t 先读取旧 t,然后执行到等号右边,执行右边的赋值语句,虽然赋值语句改变了 t 的值,但由于执行顺序,左边的 t 已经“感受”不到变化了。
bzj
   11
bzj  
   2020 年 7 月 20 日
@amiwrong123

道理我都懂,那为什么不直接 t !=tail 呢,就是为了后面可以写 t ?
mightofcode
   12
mightofcode  
   2020 年 7 月 20 日   ❤️ 2
这代码味道有点冲
xiangyuecn
   14
xiangyuecn  
   2020 年 7 月 20 日
骚操作,并没有省代码,反而理解起来异常困难。

p = (t != (t = tail)) ? t : head;

等价于:

p= t != tail ? tail:head;
t=tail;

多写一行多好理解,为了省一行写成这样也行啊:
p= t != tail ? tail:head; t=tail; 狗头
amiwrong123
   15
amiwrong123  
OP
   2020 年 7 月 20 日
@bzj
我也想知道,这可能就是大佬的写法吧。。
amiwrong123
   16
amiwrong123  
OP
   2020 年 7 月 20 日
@SingeeKing
昨天正准备开开心心看看 ConcurrentLinkedQueue 的源码,结果就被这个三木表达式给困住了
amiwrong123
   17
amiwrong123  
OP
   2020 年 7 月 20 日
@xiangyuecn
墙裂同意,理解起来太困难了。

你那个写法就很清晰了。
blessingsi
   18
blessingsi  
   2020 年 7 月 20 日
jdk 里面这种代码太多了,上次看 concurrentmap,发现给 local 变量加了锁,翻了无数遍,才发现 local 变量后面又赋值了成员变量
amiwrong123
   19
amiwrong123  
OP
   2020 年 7 月 20 日 via Android
@blessingsi
哈哈,有点好奇是哪个地方。话说你是说 ConcurrentHashMap 嘛,我也刚看了 ConcurrentHashMap,还是说你看的是那个 concurrentmap 接口
yyyyfan
   20
yyyyfan  
   2020 年 7 月 20 日 via Android
你以为的源码是大佬在写,然而其实是工人在拧螺丝
szzhiyang
   21
szzhiyang  
   2020 年 7 月 20 日
Go 代码就不会有这样晦涩难懂的表达。
amiwrong123
   22
amiwrong123  
OP
   2020 年 7 月 20 日 via Android
@yyyyfan
原来是这样的吗。可是看作者就是 Doug Lea 啊
amiwrong123
   23
amiwrong123  
OP
   2020 年 7 月 20 日 via Android
@szzhiyang
原来这就是 go 吗,爱了爱了
TtTtTtT
   24
TtTtTtT  
   2020 年 7 月 20 日
@chairuosen 正因为这不是业务代码,所以才要剩下一个 slots.
TtTtTtT
   25
TtTtTtT  
   2020 年 7 月 20 日   ❤️ 4
@xiangyuecn 不对,因为 tail 是个 mutable 的,所以每次拿的时候就要存下来,否则就会存在变化的可能。
你可以用一个新的 ref 去引用你新拿到的 tail,但是如前所说会多一个 slot 。

p = (t != (t = tail)) ? t : head;

等价于:

val tmpT = tail;
p = t != tmpT ? tmpT : head;
t = tmpT;
TtTtTtT
   26
TtTtTtT  
   2020 年 7 月 20 日
@szzhiyang 因为 JVM 字节码有解释器模型。。而 Go,不是也有 interface{}的 magic 嘛~
Jooooooooo
   27
Jooooooooo  
   2020 年 7 月 20 日   ❤️ 1
质疑别人无所谓, 但是质疑 Doug Lea 需要更多的证据.
tikazyq
   28
tikazyq  
   2020 年 7 月 20 日
这种代码是确定没有人质疑它的可读性?
misaka19000
   29
misaka19000  
   2020 年 7 月 20 日
@xiangyuecn #14 多写一行不觉得很丑吗?
misaka19000
   30
misaka19000  
   2020 年 7 月 20 日
这种代码和业务代码不一样,这种代码一旦写好基本上不存在修改的可能,所以可以降低可读性来提高代码的简洁性。
786375312123
   31
786375312123  
   2020 年 7 月 20 日
代码写的不好,和三目运算符没关系
786375312123
   32
786375312123  
   2020 年 7 月 20 日
@misaka19000 代码简洁的目的是什么?
misaka19000
   33
misaka19000  
   2020 年 7 月 20 日
@786375312123 #31 好看啊,能写一行的为啥要花两行来写
786375312123
   34
786375312123  
   2020 年 7 月 20 日
@misaka19000 好看有啥用?
misaka19000
   35
misaka19000  
   2020 年 7 月 20 日
@786375312123 #33 你要这么说那我无话可说,人各有志
786375312123
   36
786375312123  
   2020 年 7 月 20 日
@misaka19000 不是,我就是好奇,这种“一行比两行好的好看”,有啥用?
talen666
   37
talen666  
   2020 年 7 月 20 日
这不是 Lock 锁的核心代码吗~当时看的时候,也是迷糊了一会。按照括号来,后来看懂了
fakeshadow
   38
fakeshadow  
   2020 年 7 月 20 日
没写过 java,但是 cas 操作这么写很可能是有原因的。memory order 和 race 问题你用一般业务的写法有时反而更难懂
apporoad
   39
apporoad  
   2020 年 7 月 21 日
代码自带混淆
acainiao
   40
acainiao  
   2020 年 7 月 21 日 via iPhone
@TtTtTtT 代码这么写是禁忌吧
vansouth
   41
vansouth  
   2020 年 7 月 21 日
虽然我看得懂 但是我不喜欢这种写法
amiwrong123
   42
amiwrong123  
OP
   2020 年 7 月 21 日 via Android
@talen666
哪段代码,让我也康康,话说你说的是 reentrantlock 么
amiwrong123
   43
amiwrong123  
OP
   2020 年 7 月 21 日 via Android
@fakeshadow
额,可是三目表达式这里没涉及到 cas 啊,只有 valitale 读
amiwrong123
   44
amiwrong123  
OP
   2020 年 7 月 21 日 via Android
@apporoad
哈哈哈哈,老哥你挺逗
amiwrong123
   45
amiwrong123  
OP
   2020 年 7 月 21 日 via Android
@vansouth
本帖的主题可能要变成 简洁性 和 可读性 的争辩了。
yamasa
   46
yamasa  
   2020 年 7 月 21 日
我觉得那些个 conc 包的类作者就没怎么考虑过可读性。不过 comment 倒是挺足的
zhangpeter
   47
zhangpeter  
   2020 年 7 月 21 日
t != (t = tail)

等价于:


if (t != tail)
t = tail;
amiwrong123
   48
amiwrong123  
OP
   2020 年 7 月 21 日 via Android
@yamasa
嗯,好像是。尤其是无锁编程 lock free 实现的那几个类(比如那个 concurrent 跳表),不看注释根本看不懂啊
whitehack
   49
whitehack  
   2020 年 7 月 21 日
```
> let t = 2
undefined
> let tail = 3
undefined
> t!=(t=tail)
true
> t
3
>

```
2kCS5c0b0ITXE5k2
   50
2kCS5c0b0ITXE5k2  
   2020 年 7 月 21 日
@misaka19000

```
reduce(lambda x, y: x + y, map(lambda i: l[i] + 3, list(filter(lambda y: y % 2 == 0, range(len(l))))))
```
Boyce
   51
Boyce  
   2020 年 7 月 21 日
我服了,不看评论看不懂系列。
lovelive1024
   52
lovelive1024  
   2020 年 7 月 21 日
如果只一行是为了好看
p = (t != (t = tail)) ? t : hea

这种写法不是更好看
p = t != tail ? t = tail : head;

而这种可读性不是更强
p = (t != tail) ? (t = tail) : head;
atwoodSoInterest
   53
atwoodSoInterest  
   2020 年 7 月 21 日
@ily433664 如 TtTtTtT 大佬所言,tail 是可变的,这样的写法保证只会读取一次 tail,不会出现第二次读取值变了的情况。这是从根本上杜绝了问题的发生,而且减少了一个变量,是一个功能性的写法,不是为了炫技。
@TtTtTtT 感谢科普,又学到了新知识,真好
palmers
   54
palmers  
   2020 年 7 月 21 日
我做了一个测试, 估计大概就明白了.
```java
Object t = new Object();
Object h = new Object();
System.out.println("t: " + t);
System.out.println("h: " + h);
System.out.println("(t = h): " + (t = h));
```
forestn
   55
forestn  
   2020 年 7 月 21 日 via iPhone
不知道对不对 这是非阻塞算法 cas 吧
palmers
   56
palmers  
   2020 年 7 月 22 日
@palmers 我简单模拟了一下 然后反编译后字节码是这样的
源码:
```java
public void test() {
Object t = new Object();
Object h = new Object();
Object c = t != (t = h) ? t : h;
}
```
反编译后字节码:
```code
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: new #5 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: new #5 // class java/lang/Object
11: dup
12: invokespecial #1 // Method java/lang/Object."<init>":()V
15: astore_2
16: aload_1
17: aload_2
18: dup
19: astore_1
20: if_acmpeq 27
23: aload_1
24: goto 28
27: aload_2
28: astore_3
29: return
LineNumberTable:
line 15: 0
line 16: 8
line 17: 16
line 18: 29
LocalVariableTable:
Start Length Slot Name Signature
0 30 0 this Lcom/jd/pricewarning/plussdk/worker/web/Ti;
8 22 1 t Ljava/lang/Object;
16 14 2 h Ljava/lang/Object;
29 1 3 c Ljava/lang/Object;
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 27
locals = [ class java/lang/Object, class java/lang/Object ]
frame_type = 64 /* same_locals_1_stack_item */
stack = [ class java/lang/Object ]

```
fangcan
   57
fangcan  
   2020 年 7 月 22 日
这是 cas 的体现
xiangbohua
   58
xiangbohua  
   2020 年 8 月 21 日
“代码首先写给人看的,其次才是写给机器的” 这句话我十分的不同意,代码写出来不是让机器跑的,是让人天天朗读的,你当是编课本呢?
这句话之所以出名,是因为现在的憨憨程序员太多了,为了让后进来的倒霉蛋好接手一点,才制定的要求,提高所谓可读性(当然我自己也憨,也接手过别人的代码)。
然鹅,真正的大佬,都有种“老子就爱这么写,看不懂就别看,我的代码不需要维护!”的气概。。。(脑海浮现出 Linus Torvalds 指着你鼻子.jpg )
我是这么认为的
© 2026 V2EX · 73ms · 3.9.8.5