并发代码中赋值运算符的返回值

Bee*_*ope 34 java concurrency variable-assignment jls

鉴于以下课程:

class?Foo?{
  public?volatile?int?number;

  public?int?method1()?{
    int?ret?=?number?=?1;
    return?ret;
  }

  public?int?method2()?{
    int?ret?=?number?=?2;
    return?ret;
  }
}
Run Code Online (Sandbox Code Playgroud)

并且在同一个实例上调用多个线程method1()method2()同时执行Foo,对method1()的调用是否可以返回除1以外的任何内容?

Ted*_*opp 15

我认为答案取决于编译器.该语言指定:

在运行时,赋值表达式的结果是赋值发生后变量的值.

我认为理论上可以在第二个(最左边)赋值发生之前更改该值.

但是,使用Sun的javac编译器,method1将会变成:

0:   aload_0
1:   iconst_1
2:   dup_x1
3:   putfield        #2; //Field number:I
6:   istore_1
7:   iload_1
8:   ireturn
Run Code Online (Sandbox Code Playgroud)

这会复制1堆栈上的常量并将其加载到number然后ret返回之前ret.在这种情况下,如果number在分配之前修改了存储的值,则无关紧要ret,因为1没有number分配.


Bri*_*128 10

JLS 15.26规定:

有12个赋值运算符; 所有这些都是语法上的右关联(他们从右到左分组).因此,a = b = c表示a =(b = c),它将c的值赋给b,然后将b的值赋给a.

Ted Hopp的回答显示Sun的javac不遵循这种行为,可能是一种优化.

由于此处的线程,方法1的行为将是未定义的.如果Sun的编译器使行为保持不变,那么它不会破坏未定义的行为.


irr*_*ble 5

该语句包含易失性读取,或者它不包含易失性读取.这里不存在任何歧义,因为易失性读取对于编程语义非常重要.

如果javac可以信任,我们可以得出结论,该语句不涉及易失性读取number.赋值表达式x=y的值实际上只是y(转换后)的值.

我们也可以推断出这一点

    System.out.println(number=1);
Run Code Online (Sandbox Code Playgroud)

不涉及阅读 number

    String s;

    (s="hello").length();
Run Code Online (Sandbox Code Playgroud)

不涉及阅读 s

    x_1=x_2=...x_n=v
Run Code Online (Sandbox Code Playgroud)

不涉及阅读x_n, x_n-1, ...; 相反,值v直接分配给x_i(通过类型的必要转换后)x_n, ... x_i