V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  Java

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

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

    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
    >

    ```
    emeab
        50
    emeab  
       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
    我服了,不看评论看不懂系列。
    ily433664
        52
    ily433664  
       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   ·   我们的愿景   ·   实用小工具   ·   4158 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 10:16 · PVG 18:16 · LAX 03:16 · JFK 06:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.