amiwrong123
V2EX  ›  Java

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

  •  
  •   amiwrong123 · Jul 20, 2020 via Android · 10939 views
    This topic created in 2297 days ago, the information mentioned may be changed or developed.

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

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

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

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

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

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

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

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

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

    等价于:

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

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

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

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

    等价于:

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

    等价于:


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

    ```
    2kCS5c0b0ITXE5k2
    2kCS5c0b0ITXE5k2
    Jul 21, 2020
    50
    @misaka19000

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

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

    而这种可读性不是更强
    p = (t != tail) ? (t = tail) : head;
    atwoodSoInterest
    atwoodSoInterest
    Jul 21, 2020
    53
    @ily433664 如 TtTtTtT 大佬所言,tail 是可变的,这样的写法保证只会读取一次 tail,不会出现第二次读取值变了的情况。这是从根本上杜绝了问题的发生,而且减少了一个变量,是一个功能性的写法,不是为了炫技。
    @TtTtTtT 感谢科普,又学到了新知识,真好
    palmers
    palmers
    Jul 21, 2020
    54
    我做了一个测试, 估计大概就明白了.
    ```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
    forestn
    Jul 21, 2020 via iPhone
    55
    不知道对不对 这是非阻塞算法 cas 吧
    palmers
    palmers
    Jul 22, 2020
    56
    @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
    fangcan
    Jul 22, 2020
    57
    这是 cas 的体现
    xiangbohua
    xiangbohua
    Aug 21, 2020
    58
    “代码首先写给人看的,其次才是写给机器的” 这句话我十分的不同意,代码写出来不是让机器跑的,是让人天天朗读的,你当是编课本呢?
    这句话之所以出名,是因为现在的憨憨程序员太多了,为了让后进来的倒霉蛋好接手一点,才制定的要求,提高所谓可读性(当然我自己也憨,也接手过别人的代码)。
    然鹅,真正的大佬,都有种“老子就爱这么写,看不懂就别看,我的代码不需要维护!”的气概。。。(脑海浮现出 Linus Torvalds 指着你鼻子.jpg )
    我是这么认为的
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   1547 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 103ms · UTC 23:29 · PVG 07:29 · LAX 15:29 · JFK 18:29
    ♥ Do have faith in what you're doing.