静态变量是否在线程之间共享?

我的老师在Java高级线程课上说了一些我不确定的话。

他说,下面的代码不一定会更新变量。根据他的说法,这两个线程不一定共享静态变量,特别是在每个线程(主线程与)在其自己的处理器上运行,因此不共享相同的寄存器/缓存/等并且一个CPU不会更新另一个CPU的情况下。readyReaderThread

从本质上讲,他说有可能在主线程中更新,但不在中更新,因此将无限循环。readyReaderThreadReaderThread

他还声称该程序可以打印或.我知道如何打印,但不是。他提到,当变量设置为默认值时,情况就是如此。042420number

我想也许不能保证静态变量在线程之间更新,但这对Java来说非常奇怪。制作易失性可以纠正这个问题吗?ready

他展示了这个代码:

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;  
    }  
}

答案 1

在可见性方面,静态变量没有什么特别之处。如果它们可访问,则任何线程都可以访问它们,因此您更有可能看到并发问题,因为它们更容易暴露。

JVM 的内存模型存在可见性问题。这是一篇关于内存模型以及如何使写入对线程可见的文章。你不能指望一个线程使更改及时对其他线程可见(实际上,JVM 没有义务在任何时间范围内使这些更改对您可见),除非您建立“发生前”关系

以下是该链接的引用(在Jed Wesley-Smith的评论中提供):

Java 语言规范的第 17 章定义了内存操作(如读取和写入共享变量)的发生前关系。仅当写入操作发生时(在读取操作之前),一个线程的写入结果才保证对另一个线程的读取可见。同步和易失性构造,以及 Thread.start() 和 Thread.join() 方法,可以形成发生之前的关系。特别:

  • 线程中的每个操作都发生在该线程中的每个操作之前,这些操作在程序顺序的后面出现。

  • 监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁定(同步块或方法条目)之前。由于“发生前”关系是可传递的,因此线程在解锁之前的所有操作都发生在监视器的任何线程锁定之后的所有操作之前。

  • 对易失性字段的写入发生在每次后续读取该字段之前。易失性字段的写入和读取与进入和退出监视器具有相似的内存一致性效果,但不需要互斥锁定。

  • 在线程上启动的调用发生在已启动线程中的任何操作之前。

  • 线程中的所有操作都发生 - 在任何其他线程成功从该线程上的联接返回之前。


答案 2

他说的是能见度,不要太字面化。

静态变量确实在线程之间共享,但是在一个线程中所做的更改可能不会立即对另一个线程可见,因此看起来该变量有两个副本。

本文提出了一个与他呈现信息的方式一致的观点:

首先,您必须对Java内存模型有所了解。这些年来,我一直在努力简要而很好地解释它。截至今天,我能想到的最好的描述方式是,如果你这样想象它:

  • Java中的每个线程都发生在一个单独的内存空间中(这显然是不真实的,所以请耐心等待我)。

  • 您需要使用特殊机制来保证这些线程之间进行通信,就像在消息传递系统上一样。

  • 在一个线程中发生的内存写入可以“泄漏”并被另一个线程看到,但这绝不是保证的。如果没有显式通信,您就无法保证其他线程可以看到哪些写入,甚至无法保证它们被看到的顺序。

...

thread model

但同样,这只是一个考虑线程和易失性的心智模型,而不是JVM的工作原理。


推荐