我现在连个三目表达式都看不懂了… - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX    Java

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

  •  
  •   amiwrong123 2020-07-20 12:01:50 +08:00 via Android 9912 次点击
    这是一个创建于 1922 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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-07-20 12:18:04 +08:00   1
    =。=本质理解错了呀。。
    x = (t != (t = tail))

    newT = tail
    x = t != newT
    t = newT
    amiwrong123
        2
    amiwrong123  
    OP
       2020-07-20 12:44:06 +08:00 via Android
    @TtTtTtT
    你意思这个执行过程是从左到右,前一秒等号左右两边都是旧 t,后一秒等号左边是旧 t 右边是新 t

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

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

    @chairuosen JDK 里这种写法很多,这样编译的时候会减少 slots 的占用。
    TtTtTtT
        7
    TtTtTtT  
       2020-07-20 13:48:18 +08:00
    @amiwrong123 写错了,a = 1 返回 1 。
    hangs
        8
    hangs  
       2020-07-20 13:56:29 +08:00   1
    这里有个说明,我觉得说的很明白了

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

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

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

    道理我都懂,那为什么不直接 t !=tail 呢,就是为了后面可以写 t ?
    mightofcode
        12
    mightofcode  
       2020-07-20 16:53:41 +08:00   2
    这代码味道有点冲
    xiangyuecn
        14
    xiangyuecn  
       2020-07-20 17:04:43 +08:00
    骚操作,并没有省代码,反而理解起来异常困难。

    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-07-20 17:15:19 +08:00
    @bzj
    我也想知道,这可能就是大佬的写法吧。。
    amiwrong123
        16
    amiwrong123  
    OP
       2020-07-20 17:16:27 +08:00
    @SingeeKing
    昨天正准备开开心心看看 ConcurrentLinkedQueue 的源码,结果就被这个三木表达式给困住了
    amiwrong123
        17
    amiwrong123  
    OP
       2020-07-20 17:17:23 +08:00
    @xiangyuecn
    墙裂同意,理解起来太困难了。

    你那个写法就很清晰了。
    blessingsi
        18
    blessingsi  
       2020-07-20 17:27:36 +08:00
    jdk 里面这种代码太多了,上次看 concurrentmap,发现给 local 变量加了锁,翻了无数遍,才发现 local 变量后面又赋值了成员变量
    amiwrong123
        19
    amiwrong123  
    OP
       2020-07-20 17:43:45 +08:00 via Android
    @blessingsi
    哈哈,有点好奇是哪个地方。话说你是说 ConcurrentHashMap 嘛,我也刚看了 ConcurrentHashMap,还是说你看的是那个 concurrentmap 接口
    yyyyfan
        20
    yyyyfan  
       2020-07-20 18:10:33 +08:00 via Android
    你以为的源码是大佬在写,然而其实是工人在拧螺丝
    szzhiyang
        21
    szzhiyang  
       2020-07-20 18:13:50 +08:00
    Go 代码就不会有这样晦涩难懂的表达。
    amiwrong123
        22
    amiwrong123  
    OP
       2020-07-20 18:14:06 +08:00 via Android
    @yyyyfan
    原来是这样的吗。可是看作者就是 Doug Lea 啊
    amiwrong123
        23
    amiwrong123  
    OP
       2020-07-20 18:31:26 +08:00 via Android
    @szzhiyang
    原来这就是 go 吗,爱了爱了
    TtTtTtT
        24
    TtTtTtT  
       2020-07-20 20:24:46 +08:00
    @chairuosen 正因为这不是业务代码,所以才要剩下一个 slots.
    TtTtTtT
        25
    TtTtTtT  
       2020-07-20 20:27:40 +08:00   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-07-20 20:29:28 +08:00
    @szzhiyang 因为 JVM 字节码有解释器模型。。而 Go,不是也有 interface{}的 magic 嘛~
    Jooooooooo
        27
    Jooooooooo  
       2020-07-20 20:29:35 +08:00   1
    质疑别人无所谓, 但是质疑 Doug Lea 需要更多的证据.
    tikazyq
        28
    tikazyq  
       2020-07-20 21:20:32 +08:00
    这种代码是确定没有人质疑它的可读性?
    misaka19000
        29
    misaka19000  
       2020-07-20 21:31:22 +08:00
    @xiangyuecn #14 多写一行不觉得很丑吗?
    misaka19000
        30
    misaka19000  
       2020-07-20 21:33:13 +08:00
    这种代码和业务代码不一样,这种代码一旦写好基本上不存在修改的可能,所以可以降低可读性来提高代码的简洁性。
    786375312123
        31
    786375312123  
       2020-07-20 21:34:11 +08:00
    代码写的不好,和三目运算符没关系
    786375312123
        32
    786375312123  
       2020-07-20 21:34:41 +08:00
    @misaka19000 代码简洁的目的是什么?
    misaka19000
        33
    misaka19000  
       2020-07-20 21:37:32 +08:00
    @786375312123 #31 好看啊,能写一行的为啥要花两行来写
    786375312123
        34
    786375312123  
       2020-07-20 21:38:52 +08:00
    @misaka19000 好看有啥用?
    misaka19000
        35
    misaka19000  
       2020-07-20 21:48:10 +08:00
    @786375312123 #33 你要这么说那我无话可说,人各有志
    786375312123
        36
    786375312123  
       2020-07-20 21:49:36 +08:00
    @misaka19000 不是,我就是好奇,这种“一行比两行好的好看”,有啥用?
    talen666
        37
    talen666  
       2020-07-20 22:05:51 +08:00
    这不是 Lock 锁的核心代码吗~当时看的时候,也是迷糊了一会。按照括号来,后来看懂了
    fakeshadow
        38
    fakeshadow  
       2020-07-20 22:19:01 +08:00
    没写过 java,但是 cas 操作这么写很可能是有原因的。memory order 和 race 问题你用一般业务的写法有时反而更难懂
    apporoad
        39
    apporoad  
       2020-07-21 04:47:38 +08:00
    代码自带混淆
    acainiao
        40
    acainiao  
       2020-07-21 09:17:43 +08:00 via iPhone
    @TtTtTtT 代码这么写是禁忌吧
    vansouth
        41
    vansouth  
       2020-07-21 10:19:00 +08:00
    虽然我看得懂 但是我不喜欢这种写法
    amiwrong123
        42
    amiwrong123  
    OP
       2020-07-21 10:31:45 +08:00 via Android
    @talen666
    哪段代码,让我也康康,话说你说的是 reentrantlock 么
    amiwrong123
        43
    amiwrong123  
    OP
       2020-07-21 10:33:05 +08:00 via Android
    @fakeshadow
    额,可是三目表达式这里没涉及到 cas 啊,只有 valitale 读
    amiwrong123
        44
    amiwrong123  
    OP
       2020-07-21 10:34:54 +08:00 via Android
    @apporoad
    哈哈哈哈,老哥你挺逗
    amiwrong123
        45
    amiwrong123  
    OP
       2020-07-21 10:37:01 +08:00 via Android
    @vansouth
    本帖的主题可能要变成 简洁性 和 可读性 的争辩了。
    yamasa
        46
    yamasa  
       2020-07-21 10:57:39 +08:00
    我觉得那些个 conc 包的类作者就没怎么考虑过可读性。不过 comment 倒是挺足的
    zhangpeter
        47
    zhangpeter  
       2020-07-21 10:59:14 +08:00
    t != (t = tail)

    等价于:


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

    ```
    2kCS5c0b0ITXE5k2
        50
    2kCS5c0b0ITXE5k2  
       2020-07-21 14:19:55 +08:00
    @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-07-21 14:59:29 +08:00
    我服了,不看评论看不懂系列。
    lovelive1024
        52
    lovelive1024  
       2020-07-21 15:18:46 +08:00
    如果只一行是为了好看
    p = (t != (t = tail)) ? t : hea

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

    而这种可读性不是更强
    p = (t != tail) ? (t = tail) : head;
    atwoodSoInterest
        53
    atwoodSoInterest  
       2020-07-21 15:41:51 +08:00
    @ily433664 如 TtTtTtT 大佬所言,tail 是可变的,这样的写法保证只会读取一次 tail,不会出现第二次读取值变了的情况。这是从根本上杜绝了问题的发生,而且减少了一个变量,是一个功能性的写法,不是为了炫技。
    @TtTtTtT 感谢科普,又学到了新知识,真好
    palmers
        54
    palmers  
       2020-07-21 16:16:48 +08:00
    我做了一个测试, 估计大概就明白了.
    ```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-07-21 16:21:35 +08:00 via iPhone
    不知道对不对 这是非阻塞算法 cas 吧
    palmers
        56
    palmers  
       2020-07-22 09:17:31 +08:00
    @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-07-22 15:44:33 +08:00
    这是 cas 的体现
    xiangbohua
        58
    xiangbohua  
       2020-08-21 17:00:28 +08:00
    “代码首先写给人看的,其次才是写给机器的” 这句话我十分的不同意,代码写出来不是让机器跑的,是让人天天朗读的,你当是编课本呢?
    这句话之所以出名,是因为现在的憨憨程序员太多了,为了让后进来的倒霉蛋好接手一点,才制定的要求,提高所谓可读性(当然我自己也憨,也接手过别人的代码)。
    然鹅,真正的大佬,都有种“老子就爱这么写,看不懂就别看,我的代码不需要维护!”的气概。。。(脑海浮现出 Linus Torvalds 指着你鼻子.jpg )
    我是这么认为的
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     862 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 115ms UTC 21:24 PVG 05:24 LAX 14:24 JFK 17:24
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86