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

don*_*ata 92 java concurrency static multithreading memory-visibility

我的老师在一个关于线程的上层Java课上说了一些我不确定的东西.

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

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

他还声称该程序可以打印042.我知道如何42打印,但不是0.他提到当number变量设置为默认值时会出现这种情况.

我想也许并不能保证在线程之间更新静态变量,但这对我来说非常奇怪.制作readyvolatile会纠正这个问题吗?

他展示了这段代码:

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;  
    }  
}
Run Code Online (Sandbox Code Playgroud)

Nat*_*hes 74

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

JVM的内存模型存在可见性问题.这是一篇文章,讨论内存模型以及如何使写入对线程可见.您不能指望一个线程及时变得对其他线程可见的更改(实际上JVM没有义务在任何时间框架内对您进行任何可见的更改),除非您建立一个先发生过的关系.

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

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

  • 线程中的每个动作都发生在该线程中的每个动作之前,该动作在程序的顺序中稍后出现.

  • 监视器的解锁(同步块或方法退出)发生在同一监视器的每个后续锁定(同步块或方法入口)之前.并且由于之前发生的关系是可传递的,因此在解锁之前线程的所有操作都会发生 - 在任何线程锁定该监视器之后的所有操作之前.

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

  • 在启动线程中的任何操作之前发生对线程启动的调用.

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

  • 关于最终变得可见的部分是错误的.如果没有任何明确的事先发生的关系,则无法保证任何写入都会被另一个线程看到,因为JIT完全有权将读取强制写入寄存器,然后您将永远不会看到任何更新.任何最终的负载都是运气,不应该依赖. (5认同)
  • 此外,这证明了另一种反模式.不要使用volatile来保护多个共享状态.在这里,number和ready是两个状态,并且要一致地更新/读取它们,您需要实际同步. (4认同)
  • 在实践中,"及时"和"永远"是同义词.上述代码很可能永远不会终止. (3认同)
  • “除非您使用volatile关键字或进行同步。” 应该阅读“除非作者与读者之间存在相关的事前关系”和以下链接:http://download.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary。 html#MemoryVisibility (2认同)
  • @bestsss被发现。不幸的是,ThreadGroup被破坏的方式很多。 (2认同)

Ber*_*t F 36

他在谈论能见度,而不是太过于字面意思.

静态变量确实在线程之间共享,但是在一个线程中进行的更改可能不会立即对另一个线程可见,使得看起来有两个变量副本.

本文提供了一个与他如何呈现信息一致的视图:

首先,您必须了解Java内存模型的一些内容.多年来我一直在努力解释它.截至今天,我能想到的最好的方式就是用这种方式想象它:

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

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

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

...

线程模型

但同样,这只是思考线程和易失性的心理模型,而不是字面上JVM的工作方式.


axt*_*avt 12

基本上它是真的,但实际上问题更复杂.共享数据的可见性不仅会受到CPU缓存的影响,还会受到指令的无序执行的影响.

因此,Java定义了一个内存模型,它指出线程在哪种情况下可以看到共享数据的一致状态.

在您的特定情况下,添加volatile保证可见性.


biz*_*lop 8

当然,它们是"共享的",因为它们都引用相同的变量,但它们不一定会看到彼此的更新.这适用于任何变量,而不仅仅是静态变量.

理论上,除非声明变量volatile或显式同步写入,否则另一个线程的写入可能看起来是不同的顺序.


Kir*_*oll 5

在单个类加载器中,静态字段始终是共享的。要将数据显式范围限定到线程,您需要使用类似ThreadLocal.