[英]Are static variables shared between threads?
我的高级Java课堂上有关线程的老师说了一些我不确定的东西。
他指出,以下代码不一定会更新ready
变量。 据他介绍,这两个线程不一定共享静态变量,特别是在每个线程(主线程与ReaderThread
)在其自己的处理器上运行并且因此不共享相同的寄存器/缓存/等的情况下。 CPU不会更新另一个。
从本质上讲,他说有可能在主线程中更新ready
,但在ReaderThread
不ReaderThread
,以便ReaderThread
无限循环。
他还声称该程序可以打印0
或42
。 我知道如何打印42
,而不是0
。 他提到将number
变量设置为默认值时就是这种情况。
我认为也许不能保证在线程之间更新静态变量,但是这对Java来说很奇怪。 ready
挥发物是否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章定义了内存操作(例如共享变量的读写)上的事前发生关系。 只有在写操作发生之前(在读操作之前),才能保证一个线程的写结果对另一线程的读取可见。 同步和易失的构造,以及Thread.start()和Thread.join()方法,可以形成事前关联。 尤其是:
线程中的每个动作都会发生-在该线程中的每个动作之前,该动作按程序顺序出现。
监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁定(同步块或方法入口)之前。 并且由于事前发生关系是可传递的,因此在解锁之前,线程的所有操作都发生在监视该线程的所有线程之后的所有操作之前。
在每次后续读取同一字段之前,都会对易失字段进行写操作。 易失性字段的写入和读取与进入和退出监视器具有相似的内存一致性效果,但是不需要互斥锁定。
在启动线程中的任何操作之前,都会发生对启动线程的调用。
线程中的所有操作都会发生-在任何其他线程从该线程上的联接成功返回之前。
他在谈论可见度,而不是从字面上看。
静态变量确实在线程之间共享,但是在一个线程中所做的更改可能不会立即对另一线程可见,从而使该变量看起来像有两个副本。
本文提出的观点与他介绍信息的方式一致:
首先,您必须了解一些有关Java内存模型的知识。 多年来,我一直在努力地简要解释它。 截止到今天,我能想到的最好的描述方法是,如果您这样想:
Java中的每个线程都发生在一个单独的内存空间中(这显然是不正确的,因此请耐心等待)。
您需要使用特殊的机制来确保这些线程之间进行通信,就像在消息传递系统上一样。
一个线程中发生的内存写入可能会“泄漏”并被另一个线程看到,但这绝不能保证。 如果没有明确的沟通,您将无法保证其他线程可以看到哪些写入,甚至无法保证它们被看到的顺序。
...
再次重申,这只是思考线程和易失性的思维模型,而不是JVM的工作原理。
在它们都引用同一个变量的意义上,它们是“共享的”,但是它们不一定看到彼此的更新。 这适用于任何变量,而不仅仅是静态变量。
从理论上讲,除非变量声明为volatile
或显式同步,否则另一个线程进行的写操作看起来可能是不同的顺序。
在单个类加载器内,静态字段总是共享的。 为了将数据显式作用于线程,您需要使用ThreadLocal
类的工具。
初始化静态基本类型变量时,java默认会为静态变量分配一个值
public static int i ;
当您像这样定义变量时,i = 0的默认值; 那就是为什么有可能让你为0。然后主线程将boolean ready的值更新为true。 由于ready是静态变量,因此主线程和另一个线程引用相同的内存地址,因此ready变量会更改。 因此,辅助线程从while循环中退出并显示值。 打印值时,数字的初始值为0。如果线程过程在主线程更新number变量之前经过了while循环。 那么就有可能打印0
@dontocsata,您可以回到您的老师那里再学一点:)
很少有来自现实世界的笔记,无论您看到或被告知什么。 请注意,以下词语是按照所示确切顺序针对此特殊情况的。
几乎任何已知架构下,以下2个变量都将驻留在同一高速缓存行上。
private static boolean ready;
private static int number;
Thread.exit
(主线程)被保证出口和exit
是保证造成内存围栏,由于线程组的线程去除(和许多其它问题)。 (这是一个同步调用,由于没有剩余的守护进程线程,ThreadGroup也必须终止,因此我看不到没有同步部分的单一实现方式)。
启动的线程ReaderThread
将使该进程保持活动状态,因为它不是守护进程! 因此, ready
和number
将一起刷新(如果发生上下文切换,则刷新编号或之前的编号),在这种情况下,没有任何真正的理由重新排序,至少我什至无法想到。 您将需要真正奇怪的东西才能看到42
。 再次,我假定两个静态变量都将在同一缓存行中。 我只是无法想象一个4字节长的缓存行,或者一个不会在连续区域(缓存行)中分配它们的JVM。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.