需要帮助了解java中多线程时的内存可见性问题吗

需要帮助了解java中多线程时的内存可见性问题吗,java,multithreading,Java,Multithreading,我将在实践中学习Goetze的Java并发性,并且在不使用synchronized关键字时,仍停留在共享变量的内存可见性部分 代码如下 public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() {

我将在实践中学习Goetze的Java并发性,并且在不使用synchronized关键字时,仍停留在共享变量的内存可见性部分

代码如下

public class NoVisibility {

    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while(!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

    public static void main(String[] args){
        new ReaderThread().start();
        number=42;
        ready=true;
    }
}
作者说这个类可能永远循环,因为ready的值可能永远不会被读者线程看到

我不明白这种说法

在我看来,首先主线程启动并设置数字,然后准备为true。但是另一个线程有它自己的堆栈和它自己的number和ready值,它不与主内存同步,这两个线程只有它们自己的变量副本

现在,readerthread应该永远保持在循环中。我想知道为什么
Thread.yield
变量不会屈服于主线程,然后主线程应该刷新到主内存,然后readerthread应该获取这个新值并终止循环并打印正确的值,因为它也应该同步

所以我想我有一些问题是


cpu缓存中的值与主内存刷新/同步的频率是多少

该值是否可以与主内存不同步?这也是一种可能性吗

为什么会发生这种情况

当只有一个cpu核心和一个cpu缓存时,这种内存可见性是否也会发生,还是总是发生

虽然我了解竞争条件和死锁,但我在理解内存可见性问题时遇到了一些困难。这是特定于架构的吗


cpu缓存中的值与主内存刷新/同步的频率是多少

未定义。当JLS中指定的可见性保证表示需要进行缓存刷新时,就会发生缓存刷新

该值是否可以与主内存不同步?这也是一种可能性吗

为什么会发生这种情况

一般来说,缓存被刷新是有原因的。“发生在”关系指示可能需要缓存刷新的位置

当只有一个cpu核心和一个cpu缓存时,这种内存可见性是否也会发生,还是总是发生

如果只有一个核心,则缓存刷新不是问题1

虽然我了解竞争条件和死锁,但我在理解内存可见性问题时遇到了一些困难。这是特定于架构的吗

是和否。内存可见性可能因硬件体系结构和其他因素而有所不同,但编写代码以提供定义良好的行为的方法与体系结构无关

如果您确实需要深入了解内存可见性问题,则需要了解内存模型。Goetz等人在第16章中以外行术语对其进行了描述,并在JLS中进行了规定


我想知道为什么
Thread.yield()
调用不会向主线程屈服,然后主线程应该刷新到主内存

  • Thread.yield()
    可能会让位于另一个可运行线程。但是,在调用
    yield()
    时,
    main
    线程很可能不再可运行。(或者它可能仍在运行。)

  • yield()
    不会在主线程和子线程中的任何语句之间创建一个before。如果没有关系之前发生的情况,则运行时没有义务确保主线程的赋值结果对子线程可见

  • 虽然
    Thread.yield()
    可能执行缓存刷新2,但它将刷新子线程的缓存,而不是父线程的缓存

  • 因此,子线程的循环可能会无限期地继续


    1-实际上,这可能过于简化了。例如,在一个系统中,一个内核和多个具有自己缓存的超线程,需要进行缓存刷新


    2-例如,如果
    yield()
    确实导致上下文切换,那么上下文切换通常包括缓存刷新,作为操作系统执行的线程状态保存的一部分。但是,
    yield()
    不一定会导致上下文切换。此外,JLS没有指定此方面。

    字段可见性意味着线程观察者字段值来自缓存内存,并且可以与CPU另一个核心中的其他缓存具有不同的状态。JVM不保证访问共享资源的不同线程的字段可见性,程序员需要使用sycchronized来防止读取错误的状态,或者使用volatile来保证将更改刷新到其他缓存。

    “cpu缓存中的值与主内存刷新/同步的频率是多少?”不少于内存模型要求的频率。这不是“多少次”的问题。“我想知道为什么Thread.yield变量不会屈服于主线程,然后主线程应该刷新到主内存…”可见性是关于发生之前的关系。只有当写入发生在读取之前,即创建“发生在发生之前”关系的操作时,写入才会保证可见
    Thread.yield()
    不会创建“先发生后发生”关系。我要明确补充的是,
    Thread.yield()
    不会创建“先发生后发生”关系,因为OP会问为什么这不能解决问题-实际上不需要根据文档做任何事情。即使它确实创建了一个HB,它也只会自己创建一个HB,因为在
    Thread.start()
    之后没有其他操作创建HB,而
    yield()