在Java中进行多线程处理时需要帮助了解内存可见性问题

时间:2019-09-29 07:09:29

标签: java multithreading

在实践中,我正在经历Goetze的Java并发性,并停留在关于不使用synced关键字时共享变量的内存可见性的章节。

代码如下

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的值,该值不与主存储器同步,并且这两个线程仅具有它们自己的变量副本。

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

所以我想我有一些问题。

cpu缓存中的值多久与主内存进行刷新/同步一次?

该值也可以不与主存储器同步吗?

为什么会这样?

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

尽管我了解竞争条件和死锁,但在理解内存可见性问题时还是遇到了一些麻烦。这是特定于体系结构的吗?

2 个答案:

答案 0 :(得分:1)

  

cpu缓存中的值多久与主内存进行刷新/同步一次?

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

  

值也可以不与主存储器同步吗?

是的

  

为什么会这样?

通常来说,缓存被清空是有原因的。 关系表示可能需要进行缓存刷新的位置。

  

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

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

  

尽管我了解竞争条件和死锁,但在理解内存可见性问题时还是遇到了一些麻烦。这是特定于体系结构的吗?

是,不是。内存可见性 可能根据硬件架构 的不同而有所不同,但是编写代码以提供明确定义的行为的方法与架构无关。

如果您确实需要深入了解内存可见性问题,则需要了解内存模型。它在Goetz等人的第16章中以通俗易懂的方式进行了描述,并在JLS中进行了规定。


  

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

  1. Thread.yield() 可能会产生另一个可运行线程。但是,在调用yield()时,很有可能main线程不再可运行。 (或者它可能仍在运行。)

  2. yield()不会在主线程和子线程中的任何语句之间创建先发生。如果没有 happens-before 关系,则运行时不必确保子线程可以看到主线程的赋值结果。

  3. Thread.yield() 可能执行缓存刷新 2 时,它将是子线程缓存的刷新,而不是父线程缓存的刷新。

因此,子线程的循环 可以无限期地继续。


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

2-例如,如果yield() 导致上下文切换,则上下文切换通常将缓存刷新作为一部分由操作系统执行的线程状态保存。但是,yield()不一定会导致上下文切换。此外,JLS并未指定这方面。

答案 1 :(得分:0)

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