线程之间是否共享静态变量?
我在一个关于线程的高级java类的老师说了一些我不确定的东西。
他表示,下面的代码不一定会更新ready
变量。 据他介绍,这两个线程不一定共享静态变量,特别是在每个线程(主线程与ReaderThread)在其自己的处理器上运行并且因此不共享相同的寄存器/缓存/等等以及一个CPU不会更新其他。
从本质上讲,他说主线程中ready
可能被更新,而不是在ReaderThread中,所以ReaderThread将无限循环。 他还声称该程序可以打印'0'或'42'。 我了解'42'是如何打印的,但不是'0'。 他提到这是number
变量设置为默认值时的情况。
我想也许不能保证静态变量在线程间更新,但这对我来说很奇怪。 是否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; } }
没有特定于静态变量的可见性问题。 JVM的内存模型存在可视性问题。 这里有一篇关于内存模型的文章,以及如何让线程看到写入。 你不能指望一个线程及时看到其他线程可以看到的变化(实际上JVM没有义务让这些变化对你完全可见),除非你建立了一个发生之前的关系,这里有一个引用该链接(由Jed Wesley-Smith在评论中提供):
Java语言规范的第17章定义了关于内存操作(如读取和写入共享变量)的happen-before关系。 只有在写入操作发生时,一个线程写入的结果才能保证对另一个线程的读取可见 - 在读取操作之前。 synchronized和volatile结构以及Thread.start()和Thread.join()方法可以形成事前关系。 尤其是:
线程中的每个动作都发生在该线程中的每个动作之前,该动作稍后会按程序的顺序进行。
监视器的解锁(同步块或方法退出)发生在相同监视器的每个后续锁定(同步块或方法输入)之前。 并且因为发生在before关系是传递性的,所以在解锁之前的线程的所有动作发生在任何线程锁定该监视器之后的所有动作之前。
写入易失性字段发生 - 在每次后续读取相同字段之前。 写入和读取易失性字段具有与进入和退出监视器类似的内存一致性效果,但不需要互斥锁定。
在启动线程中的任何操作之前,都会发生在线程上启动的调用。
在任何其他线程从该线程上的连接成功返回之前,线程中的所有操作都会发生。
他在谈论能见度,而不是从字面上理解。
静态变量实际上是在线程之间共享的,但是在一个线程中所做的更改可能不会立即对另一个线程可见,这看起来好像有两个变量副本。
本文提供了一个与他如何呈现信息一致的视图:
首先,你必须了解一些关于Java内存模型的内容。 多年来我一直在努力解释这个问题。 截至今天,我能想到的最好的方式来描述它,如果你这样想:
Java中的每个线程都在单独的内存空间中进行(这显然是不真实的,所以请耐心等待)。
您需要使用特殊的机制来保证这些线程之间的通信发生,就像在消息传递系统上一样。
内存写入发生在一个线程中可以“泄漏”并被另一个线程看到,但这并不是保证。 如果没有明确的沟通,你不能保证哪些写入被其他线程看到,甚至不能保证他们看到的顺序。
...
但是,这只是考虑线程和易变性的心智模型,而不是字面意义上的JVM如何工作。
基本上这是真的,但实际上问题更复杂。 共享数据的可视性不仅受CPU高速缓存的影响,还受到指令无序执行的影响。
因此Java定义了一个内存模型,它指出线程在哪种情况下可以看到共享数据的一致状态。
在您的特定情况下,添加volatile
保证可见性。
上一篇: Are static variables shared between threads?
下一篇: How do I use an equivalent to C++ reference parameters in Java?