为什么subthread看不到改变的静态变量? JMM还是不稳定的

时间:2018-03-20 12:34:52

标签: java multithreading jvm volatile

以下是volatile关键字的使用示例。

public class Test3{
    public static volatile boolean stop = false;// if volatile is not set, the loop will not stop
    public static void main(String[] args) throws InterruptedException{
        Thread thread = new Thread(){
            public void run() {
                int i=0;
                while(!stop){
                    i++;
                    // add this line
//                  System.out.println(i);
                    // or this block
//                  try {
//                      Thread.sleep(1);
//                  } catch (InterruptedException e) {
//                      e.printStackTrace();
//                  }
                }
            }
        };
        thread.start();
        Thread.sleep(2000);
        stop = true;
    }
}

很容易理解,如果设置volatile,JVM应该在检查其值时从内存加载更新的值,然后while循环可以按预期停止。但问题是,静态变量不应该同时改变吗?可能会有一些延迟,但最终应该检测到这种变化。没有?我已经测试过,如果我们添加一些打印代码或睡眠代码,可以检测到这种变化吗?有人可以教我为什么喜欢这样吗?也许是关于JMM。

2 个答案:

答案 0 :(得分:3)

从时钟时间的角度来看,时间对于内存可见性没有意义。重要的是同步操作之间的同步顺序。读取和写入非volatile字段不是同步操作,因此在没有任何其他同步操作的情况下,它们之间没有排序。

所以即使主线程在一年前完成,所以写入必须从主线程的角度来看,子线程可以继续运行,永远运行;写作没有从它的角度发生。它也不知道主线程已经终止。请注意,执行能够检测到其他线程已终止的操作是可以建立订单的同步操作。

但是由于实际的程序行为也依赖于JIT编译器和优化器,因此某些代码更改可能会产生影响,即使它不能得到保证。

E.g。插入sleep并不意味着任何内存可见性:

JLS §17.3. Sleep and Yield
  

值得注意的是,Thread.sleepThread.yield都没有任何同步语义。特别是,在调用Thread.sleepThread.yield之前,编译器不必将寄存器中缓存的写入刷新到共享内存,编译器调用{之后也不必重新加载缓存在寄存器中的值{1}}或Thread.sleep

但它可能会阻止优化器将循环视为需要优化的热点。

当您插入Thread.yield语句时,System.out.println的内部同步可能会影响整体内存可见性,但这种效果也无法保证,因为主线程不会同步在PrintStream上。

顺便说一下,甚至没有保证在相同优先级的线程之间发生抢先线程切换。因此,如果JVM在将CPU返回主线程之前尝试在调用PrintStream之后完成子线程,那将是一个有效的执行。

在该执行方案中,循环中没有start(),子线程永远不会放弃CPU,因此sleep永远不会设置为stop,即使声明为{{ 1}}。这将是避免轮询循环的另一个原因,尽管可能没有抢先的线程切换的真实生活执行环境。今天的大多数执行环境甚至都有多个CPU,所以不放弃CPU不会阻止其他线程执行。

但是,为了正式更正,您应该在写入true变量和读取变量之间执行排序,例如声明变量volatile并插入可能导致变量的操作当stop仍为volatile时,线程最终会释放CPU。

答案 1 :(得分:2)

  

很容易理解,如果设置了volatile,JVM应该加载   在检查其值时从内存更新值,然后是while   循环可以按预期停止。

volatile是某种rulemechanism,而不是上面的具体实施。它用于在线程之间建立happends-before关系:

  

使用volatile变量可降低内存一致性的风险   错误,因为对volatile变量的任何写入都会建立一个   在与之后的相关读取之前发生   变量。 这意味着始终对volatile变量进行更改   其他线程可见。更重要的是,这也意味着当a   线程读取一个volatile变量,它不仅看到最新的变化   对于挥发性,还有导致代码的副作用   变化

如果没有volatile或其他同步,静态变量的更新可能会被其他线程看到延迟,并且无法永远看到,因为{ {3}}。 不确定。即使你添加一些打印代码或睡眠代码,并发现它有效,但这并不意味着它仍然可以在其他环境或其他时刻工作。

但是,如果您在while loopmain帖子中添加打印代码:

while(!stop){
    i++;
    System.out.println(i);
}

stop = true;
System.out.println("some");

JMM可以保证在循环中检测到stop = true(至少在oracle jdk 8u161上),这是因为System.out.printlnmemory barriar,也可以构建synchronized 1}}涉及的线程之间的关系,请参阅源代码:

happens-before