关于java:线程之间是否共享静态变量?

Are static variables shared between threads?

我的老师在一个上层的Java类线程上说了一些我不确定的事情。

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

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

他还声称,该计划有可能打印042。我了解如何打印42,但不了解0。他提到,当number变量设置为默认值时,就会出现这种情况。

我想也许不能保证静态变量在线程之间更新,但这对Java来说是非常奇怪的。使ready不稳定是否解决了这个问题?

他展示了这个密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的评论中提供):

Chapter 17 of the Java Language Specification defines the happens-before relation on memory operations such as reads and writes of shared variables. The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation. The synchronized and volatile constructs, as well as the Thread.start() and Thread.join() methods, can form happens-before relationships. In particular:

  • Each action in a thread happens-before every action in that thread that comes later in the program's order.

  • An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.

  • A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.

  • A call to start on a thread happens-before any action in the started thread.

  • All actions in a thread happen-before any other thread successfully returns from a join on that thread.


他说的是能见度,不能太随便。

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

本文介绍了一个与他提供信息的方式一致的视图:

  • http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html

First, you have to understand a little something about the Java memory model. I've struggled a bit over the years to explain it briefly and well. As of today, the best way I can think of to describe it is if you imagine it this way:

  • Each thread in Java takes place in a separate memory space (this is clearly untrue, so bear with me on this one).

  • You need to use special mechanisms to guarantee that communication happens between these threads, as you would on a message passing system.

  • Memory writes that happen in one thread can"leak through" and be seen by another thread, but this is by no means guaranteed. Without explicit communication, you can't guarantee which writes get seen by other threads, or even the order in which they get seen.

...

thread model

但同样,这只是一个思考线程化和易失性的心理模型,而不是实际上JVM是如何工作的。


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

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

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


当然,它们是"共享"的,因为它们都引用同一个变量,但不一定看到彼此的更新。这对于任何变量都是正确的,不仅仅是静态的。

从理论上讲,由另一个线程进行的写入看起来可能是以不同的顺序进行的,除非变量声明为volatile,或者写入是显式同步的。


在单个类加载器中,静态字段总是共享的。要显式地将数据范围限定到线程,您需要使用像ThreadLocal这样的工具。


初始化静态基元类型时,变量Java默认为静态变量分配一个值。

1
public static int i ;

当您这样定义变量时,缺省值i=0;这就是为什么有可能得到0。然后,主线程将布尔值ready更新为true。因为ready是一个静态变量,所以主线程和其他线程引用了相同的内存地址,所以ready变量会发生变化。所以第二个线程从while循环和print值中退出。打印时,数值的初始化值为0。如果线程进程在主线程更新编号变量之前经过while循环。然后有可能打印0


@多托萨塔你可以回到你的老师那里,教他一点:)

很少有来自现实世界的笔记,不管你看到或听到什么。请注意,下面的文字是关于这一特殊情况的确切顺序显示。

以下2个变量将驻留在几乎任何已知体系结构下的同一缓存线上。

1
2
private static boolean ready;  
private static int number;

由于线程组线程删除(以及许多其他问题),保证Thread.exit(主线程)退出,保证exit造成内存围栏。(这是一个同步调用,我看不到使用同步部分实现的单一方法,因为如果没有留下守护进程线程,那么线程组也必须终止,等等)。

启动的线程ReaderThread将使进程保持活动状态,因为它不是守护进程!因此,readynumber将被刷新在一起(如果发生上下文切换,则是之前的数字),并且在这种情况下没有重新排序的真正原因,至少我甚至想不出一个。除了42以外,你还需要一些真正奇怪的东西。我再次假定两个静态变量将在同一缓存行中。我只是无法想象一个4字节长的缓存线或者一个不会在连续区域(缓存线)中分配它们的JVM。