Java 为什么变量在没有同步的情况下对其他线程可见?

Java 为什么变量在没有同步的情况下对其他线程可见?,java,multithreading,java-memory-model,memory-model,Java,Multithreading,Java Memory Model,Memory Model,我有一个关于记忆可见度的理论问题。以下是示例代码: 公共类TwoThreadApp{ 专用静态A类{ int x=1; } 公共静态void main(字符串[]arg)引发InterruptedException{ A=新的A(); 线程t2=新线程(()->{ while(true){ 如果(a.x==2){ 系统输出打印项次(a.x); 返回; } //使a.x对线程“t2”可见的IO操作 System.out.println(“in-loop”); } }); t2.start(); 睡

我有一个关于记忆可见度的理论问题。以下是示例代码:

公共类TwoThreadApp{
专用静态A类{
int x=1;
}
公共静态void main(字符串[]arg)引发InterruptedException{
A=新的A();
线程t2=新线程(()->{
while(true){
如果(a.x==2){
系统输出打印项次(a.x);
返回;
}
//使a.x对线程“t2”可见的IO操作
System.out.println(“in-loop”);
}
});
t2.start();
睡眠(100);
a、 x=2;
}
}
  • 如果没有
    System.out.println(“in-loop”)
    程序将无限期地工作,这是预期的行为
  • 但是对于
    System.out.println(“in-loop”)
    来说,它总是完成的,这是不期望的,因为a.x不是易失性的,并且没有同步的块
  • 我的环境:ubuntu 16.04,openjdk 1.8.0131

    为什么会这样

    如果没有System.out.println(“in-loop”),程序将无限期地工作,这是预期的行为

    相反,该计划应该退出。程序继续运行,这是
    x
    被缓存(作为非易失性)的副作用。缓存是编译器的优化,您不应该依赖它(取决于JVM设置)

    但是对于System.out.println(“in-loop”),它总是完成的,这是不期望的,因为a.x不是易失性的,并且没有同步的块

    这是我所期望的行为。我不能告诉你为什么,我假设IO操作是用来清除线程缓存的(如果有人有更好的洞察力,请评论/更正)

    在没有同步或锁定的情况下访问变量,或者在多个线程中不稳定,这可能真的是不可预测的


    您甚至可以使用
    -Djava.compiler=NONE
    禁用许多优化(那么程序应该总是按预期退出)

    我认为这与编译器优化有关。在第一个示例中,compile可以决定将If条件移动到while循环之外

    if(a.x == 2) while(true) {...}
    
    在第二种情况下,由于您使用的是内部使用同步关键字的println,编译器可能不会像上面那样优化代码

    这就是我的想法,我可能错了


    编辑:您也可以在此处引用:

    变量始终是可访问的(用于读写),因为它是静态的并且在同一类中。同步只是锁定对象以确保完整性的一种方法。因为
    x
    是缓存的,所以JVM有这种优化。主要问题是IO操作如何参与清除线程缓存。这只是我的假设。如果要反编译,println的字节码将声明
    ldc
    (加载静态数据)和
    invokevirtual
    (调用方法)。缓存实际上是由JIT(即时编译器)完成的。只需使用非易失性或同步,即可生成程序unpredictable@SergeyGalkin,它与IO无关,而是与JVM应用的before关系有关。重复响应的链接解释了println的工作原理(查找“print语句的效果”),它包括一个同步块。根据Java内存模型(请参阅。):“在进入同步块之前,我们获取监视器,其作用是使本地处理器缓存无效,以便从主存重新加载变量。”