刚学多线程,请问下面这段程序为什么停不下来啊?

新手上路,请多包涵

书上讲的不是很清楚,只说是编译器优化。不是很懂,再怎么优化ready=true不还得执行吗,怎么while(!ready)就退不出来了呢?

public class NoVisibility {
    private static boolean ready;  
    private static int number;  
  
    private static class ReaderThread extends Thread {  
        public void run() {  
            while (!ready);
            System.out.println(number);  
        }  
    } 
  
    public static void main(String[] args) throws InterruptedException {  
        new ReaderThread().start();  
        Thread.sleep(1000);
        number = 42;  
        ready = true;  
        Thread.sleep(10000);
    }  
}
阅读 2.4k
4 个回答

这个是一个比较典型的多线程可见性问题。

问题分析

你的main方法开启了一个主线程,我们暂且称其为线程A
ReaderThread.run()方法开启了线程B,我暂且称其为线程B

当线程A运行到new ReaderThread().start(); 方法时,线程B会创建。
线程B在竞争拿到CPU资源(一个核心)以后,CPU会将线程B所需要的数据(ready)放到CPU缓存中,此时线程B由于是一个死循环,所以线程B会占用一整个核心,如果你这个时候查看CPU使用情况,会发现有一个核心一直都是100%。
线程A在运行完Thread.sleep(1000);以后,会去修改ready的值,但这并不会告知线程B,线程B中的ready也不会被更新,仍然是最开始的初始值。

解决方案

java提供了volatile关键字,也就是 @听风逝夜 提到的。volatile用以处理多线程之间数据可见性问题,加了volatile关键字的变量,CPU在运算时,会直接从主存(我们常说的内存或内存条)读取。这时,当线程A修改ready的值以后,会将ready的值刷新到主存中,线程B就对这个被修改的ready可见了。

问题扩展

volatile除了解决可见性以外,还有其他作用,关于更多volatile的讲解,可以查看https://www.baeldung.com/java...

但需要注意的是,volatile只适合读多写少的使用场景,请勿滥用。

给ready加volatile

题主的问题是对 JMM 理解还不够。
这种情况就是工作内存还是 false 而没有和主内存进行同步,因此一直在循环中。
详细可以再多复习下JMM
深入理解 Java 多线程系列(1)——一个简单需求的并行改造 & Java多线程的通信问题

Java 内存模型的抽象(JMM)
类似现代多核处理器会给每个核心设计自己的 CPU 寄存器缓存主内存中的目标数据,以方便处理器的快速存取。当多个处理器的任务涉及同一块主内存时,就需要利用 MSI、MESI、MOSI 等缓存一致性协议来协调各个处理器之间的对特定内存或者高速缓存的访问规则。如下图:

image.png

针对一个线程对共享变量的写入何时对另一个线程可见问题,Java 利用 JMM 抽象了线程与主内存之间的关系。
我们先来看看Java内存模型(JMM)的示意图:

image.png

注意:这里的工作内存并不实际存在,而是涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化等概念的一种抽象

从图中就可以很清晰的归纳出,如果线程A想要和线程B之间想要通过共享内存进行通信,那么必须经过以下步骤:

线程A将工作内存中更新的工作内存副本写回至主内存中
线程B从根据主内存中的值重新更新刷新自己的工作内存副本
上述两步必须有序进行,否则将会导致通信错误。

volatile 线程可见性

推荐问题
宣传栏