Java 线程之间是否共享静态变量?
我的老师在一个高级Java线程课程上说了一些我不确定的话 他表示,以下代码不一定会更新Java 线程之间是否共享静态变量?,java,multithreading,concurrency,static,memory-visibility,Java,Multithreading,Concurrency,Static,Memory Visibility,我的老师在一个高级Java线程课程上说了一些我不确定的话 他表示,以下代码不一定会更新ready变量。据他说,这两个线程不一定共享静态变量,特别是当每个线程(主线程与ReaderThread)在自己的处理器上运行时,因此不共享相同的寄存器/cache/etc,一个CPU不会更新另一个CPU 本质上,他说有可能在主线程中更新ready,但在ReaderThread中不更新,因此ReaderThread将无限循环 他还声称该程序可以打印0或42。我了解如何打印42,但不了解0。他提到,当number
ready
变量。据他说,这两个线程不一定共享静态变量,特别是当每个线程(主线程与ReaderThread
)在自己的处理器上运行时,因此不共享相同的寄存器/cache/etc,一个CPU不会更新另一个CPU
本质上,他说有可能在主线程中更新ready
,但在ReaderThread
中不更新,因此ReaderThread
将无限循环
他还声称该程序可以打印0
或42
。我了解如何打印42
,但不了解0
。他提到,当number
变量设置为默认值时,会出现这种情况
我认为可能不能保证在线程之间更新静态变量,但这对Java来说非常奇怪。使ready
volatile可以解决此问题吗
他展示了这个代码:
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;
}
}
在单个类加载器中,静态字段总是共享的。要显式地将数据范围限定到线程,您需要使用类似于
ThreadLocal
的工具,静态变量在可见性方面没有任何特殊之处。如果它们是可访问的,任何线程都可以访问它们,因此您更可能看到并发问题,因为它们更容易暴露
JVM的内存模型带来了可见性问题。您不能指望一个线程所做的更改能够及时地对其他线程可见(实际上JVM没有义务在任何时间范围内使这些更改对您可见),除非您建立一个新的线程
以下是该链接的一段引用(由Jed Wesley Smith在评论中提供):
Java语言规范的第17章定义了内存操作(如读取和写入共享变量)上的“发生在之前”关系。只有在写操作发生在读操作之前,一个线程的写操作的结果才能保证对另一个线程的读操作可见。同步和易失性构造以及Thread.start()和Thread.join()方法可以在关系发生之前形成。特别是:
- 线程中的每个动作都发生在该线程中的每个动作之前,该线程中的每个动作都是按照程序的顺序稍后出现的
- 监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁定(同步块或方法进入)之前。而且因为“发生在之前”关系是可传递的,所以在解锁之前线程的所有操作都发生在监视的任何线程锁定之后的所有操作之前
- 对易失性字段的写入发生在对该字段的每次后续读取之前。易失性字段的写入和读取与进入和退出监视器具有类似的内存一致性效果,但不需要互斥锁定
- 对线程的启动调用发生在已启动线程中的任何操作之前
- 线程中的所有操作都发生在任何其他线程从该线程上的联接成功返回之前
基本上是这样,但实际上问题更复杂。共享数据的可见性不仅会受到CPU缓存的影响,还会受到指令无序执行的影响 所以Java定义了一个状态,在这种情况下线程可以看到共享数据的一致状态 在您的特定情况下,添加
volatile
可以保证可见性。它们当然是“共享”的,因为它们都引用相同的变量,但不一定能看到彼此的更新。这适用于任何变量,而不仅仅是静态变量
从理论上讲,由另一个线程进行的写操作可能会以不同的顺序出现,除非变量被声明为volatile,或者写操作是显式同步的。他说的是可见性,不要太过直截了当
静态变量确实在线程之间共享,但在一个线程中所做的更改可能不会立即对另一个线程可见,这使得该变量似乎有两个副本
这篇文章提出了一个与他如何陈述信息一致的观点:
- Java中的每个线程都发生在一个单独的内存空间中(这显然是不真实的,所以请记住这一点)
- 您需要使用特殊的机制来保证这些线程之间的通信,就像在消息传递系统上一样
- 发生在一个线程中的内存写入可能会“泄漏”,并被另一个线程看到,但这并不能保证。如果没有明确的通信,您无法保证其他线程可以看到哪些写入,甚至无法保证它们被看到的顺序
private static boolean ready;
private static int number;
Thread.exit
(主线程)保证退出并退出
public static int i ;