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
1
TtTtTtT 2020-07-20 12:18:04 +08:00 1
=。=本质理解错了呀。。
x = (t != (t = tail)) 是 newT = tail x = t != newT t = newT |
2
amiwrong123 OP |
3
zmj1316 2020-07-20 12:44:33 +08:00 via Android
取值有顺序的,左边的 t 早于右边的赋值吧
|
4
no1xsyzy 2020-07-20 13:00:42 +08:00
话说这也不是三目看不懂啊……
|
5
chairuosen 2020-07-20 13:11:21 +08:00 3
谁这么写代码我打死他,相当于 get 里干 set 的活
|
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 的占用。 |
7
TtTtTtT 2020-07-20 13:48:18 +08:00
@amiwrong123 写错了,a = 1 返回 1 。
|
8
hangs 2020-07-20 13:56:29 +08:00 1
这里有个说明,我觉得说的很明白了
https://segmentfault.com/q/1010000022097461/a-1020000022098182 我理解一个是赋值表达式本身是有返回的,返回值正如 @TtTtTtT 所说,是被赋值的值,另一个是开头的 t,其实读的还是旧值 |
9
chairuosen 2020-07-20 14:01:38 +08:00
@TtTtTtT #6 写代码第一给人看,第二才是给机器看。如果是底层万年不变的可以用写奇技淫巧,业务代码这么写被喷成狗
|
10
amiwrong123 OP @TtTtTtT
赋值语句我懂了,而且这个比较运算之前我好像理解错了。 (t != (t = tail)) 实际上是左边的 t 先读取旧 t,然后执行到等号右边,执行右边的赋值语句,虽然赋值语句改变了 t 的值,但由于执行顺序,左边的 t 已经“感受”不到变化了。 |
11
bzj 2020-07-20 15:36:42 +08:00
|
12
mightofcode 2020-07-20 16:53:41 +08:00 2
这代码味道有点冲
|
13
SingeeKing 2020-07-20 16:59:12 +08:00
|
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; 狗头 |
15
amiwrong123 OP @bzj
我也想知道,这可能就是大佬的写法吧。。 |
16
amiwrong123 OP @SingeeKing
昨天正准备开开心心看看 ConcurrentLinkedQueue 的源码,结果就被这个三木表达式给困住了 |
17
amiwrong123 OP |
18
blessingsi 2020-07-20 17:27:36 +08:00
jdk 里面这种代码太多了,上次看 concurrentmap,发现给 local 变量加了锁,翻了无数遍,才发现 local 变量后面又赋值了成员变量
|
19
amiwrong123 OP @blessingsi
哈哈,有点好奇是哪个地方。话说你是说 ConcurrentHashMap 嘛,我也刚看了 ConcurrentHashMap,还是说你看的是那个 concurrentmap 接口 |
20
yyyyfan 2020-07-20 18:10:33 +08:00 via Android
你以为的源码是大佬在写,然而其实是工人在拧螺丝
|
21
szzhiyang 2020-07-20 18:13:50 +08:00
Go 代码就不会有这样晦涩难懂的表达。
|
22
amiwrong123 OP @yyyyfan
原来是这样的吗。可是看作者就是 Doug Lea 啊 |
23
amiwrong123 OP @szzhiyang
原来这就是 go 吗,爱了爱了 |
24
TtTtTtT 2020-07-20 20:24:46 +08:00
@chairuosen 正因为这不是业务代码,所以才要剩下一个 slots.
|
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; |
27
Jooooooooo 2020-07-20 20:29:35 +08:00 1
质疑别人无所谓, 但是质疑 Doug Lea 需要更多的证据.
|
28
tikazyq 2020-07-20 21:20:32 +08:00
这种代码是确定没有人质疑它的可读性?
|
29
misaka19000 2020-07-20 21:31:22 +08:00
@xiangyuecn #14 多写一行不觉得很丑吗?
|
30
misaka19000 2020-07-20 21:33:13 +08:00
这种代码和业务代码不一样,这种代码一旦写好基本上不存在修改的可能,所以可以降低可读性来提高代码的简洁性。
|
31
786375312123 2020-07-20 21:34:11 +08:00
代码写的不好,和三目运算符没关系
|
32
786375312123 2020-07-20 21:34:41 +08:00
@misaka19000 代码简洁的目的是什么?
|
33
misaka19000 2020-07-20 21:37:32 +08:00
@786375312123 #31 好看啊,能写一行的为啥要花两行来写
|
34
786375312123 2020-07-20 21:38:52 +08:00
@misaka19000 好看有啥用?
|
35
misaka19000 2020-07-20 21:48:10 +08:00
@786375312123 #33 你要这么说那我无话可说,人各有志
|
36
786375312123 2020-07-20 21:49:36 +08:00
@misaka19000 不是,我就是好奇,这种“一行比两行好的好看”,有啥用?
|
37
talen666 2020-07-20 22:05:51 +08:00
这不是 Lock 锁的核心代码吗~当时看的时候,也是迷糊了一会。按照括号来,后来看懂了
|
38
fakeshadow 2020-07-20 22:19:01 +08:00
没写过 java,但是 cas 操作这么写很可能是有原因的。memory order 和 race 问题你用一般业务的写法有时反而更难懂
|
39
apporoad 2020-07-21 04:47:38 +08:00
代码自带混淆
|
41
vansouth 2020-07-21 10:19:00 +08:00
虽然我看得懂 但是我不喜欢这种写法
|
42
amiwrong123 OP @talen666
哪段代码,让我也康康,话说你说的是 reentrantlock 么 |
43
amiwrong123 OP @fakeshadow
额,可是三目表达式这里没涉及到 cas 啊,只有 valitale 读 |
44
amiwrong123 OP @apporoad
哈哈哈哈,老哥你挺逗 |
45
amiwrong123 OP @vansouth
本帖的主题可能要变成 简洁性 和 可读性 的争辩了。 |
46
yamasa 2020-07-21 10:57:39 +08:00
我觉得那些个 conc 包的类作者就没怎么考虑过可读性。不过 comment 倒是挺足的
|
47
zhangpeter 2020-07-21 10:59:14 +08:00
t != (t = tail)
等价于: if (t != tail) t = tail; |
48
amiwrong123 OP @yamasa
嗯,好像是。尤其是无锁编程 lock free 实现的那几个类(比如那个 concurrent 跳表),不看注释根本看不懂啊 |
49
whitehack 2020-07-21 13:40:52 +08:00
```
> let t = 2 undefined > let tail = 3 undefined > t!=(t=tail) true > t 3 > ``` |
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)))))) ``` |
51
Boyce 2020-07-21 14:59:29 +08:00
我服了,不看评论看不懂系列。
|
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; |
53
atwoodSoInterest 2020-07-21 15:41:51 +08:00
|
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)); ``` |
55
forestn 2020-07-21 16:21:35 +08:00 via iPhone
不知道对不对 这是非阻塞算法 cas 吧
|
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 ] ``` |
57
fangcan 2020-07-22 15:44:33 +08:00
这是 cas 的体现
|
58
xiangbohua 2020-08-21 17:00:28 +08:00
“代码首先写给人看的,其次才是写给机器的” 这句话我十分的不同意,代码写出来不是让机器跑的,是让人天天朗读的,你当是编课本呢?
这句话之所以出名,是因为现在的憨憨程序员太多了,为了让后进来的倒霉蛋好接手一点,才制定的要求,提高所谓可读性(当然我自己也憨,也接手过别人的代码)。 然鹅,真正的大佬,都有种“老子就爱这么写,看不懂就别看,我的代码不需要维护!”的气概。。。(脑海浮现出 Linus Torvalds 指着你鼻子.jpg ) 我是这么认为的 |