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

时间:2021-06-12 13:24:54

My teacher in an upper level java class on threading said something that I wasn't sure of.

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

He stated that the following code would not necessarily update the ready variable. According to him, the two threads don't necessarily share the static variable, specifically in the case when each thread (main thread versus ReaderThread) is running on its own processor and therefore doesn't share the same registers/cache/etc and one CPU won't update the other.

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

Essentially, he said it is possible that ready is updated in the main thread, but NOT in the ReaderThread, so that ReaderThread will loop infinitely. He also claimed it was possible for the program to print '0' or '42'. I understand how '42' could be printed, but not '0'. He mentioned this would be the case when the number variable is set to the default value.

本质上,他说有可能在主线程中更新ready,但不是在ReaderThread中,因此ReaderThread将无限循环。他还声称该程序可以打印'0'或'42'。我知道如何打印'42',但不能打印'0'。他提到当数字变量设置为默认值时会出现这种情况。

I thought perhaps it is not guaranteed that the static variable is updated between the threads, but this strikes me as very odd for Java. Does making ready volatile correct this problem?

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

He showed this code:

他展示了这段代码:

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;  
    }  
}

7 个解决方案

#1


57  

There is no visibility issue specific to static variables. There is a visibility issue imposed by the JVM's memory model. Here's an article talking about the memory model and how writes become visible to threads. You can't count on changes one thread makes becoming visible to other threads in a timely manner (actually the JVM has no obligation to make those changes visible to you at all), unless you establish a happens-before relationship, here's a quote from that link (supplied in the comment by Jed Wesley-Smith):

静态变量没有特定的可见性问题。 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:

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

  • 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.

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

#2


28  

He was talking about visibility and not to be taken too literally.

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

Static variables are indeed shared between threads, but the changes made in one thread may not be visible to another thread immediately, making it seem like there are two copies of the variable.

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

This article presents a view that is consistent with how he presented the info:

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

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:

首先,您必须了解Java内存模型的一些内容。多年来我一直在努力解释一下这个问题。截至今天,我能想到描述它的最佳方式是你是这样想象的:

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

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

  • 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.

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

...

...

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

But again, this is simply a mental model to think about threading and volatile, not literally how the JVM works.

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

#3


9  

Basically it's true, but actually the problem is more complex. Visibility of shared data can be affected not only by CPU caches, but also by out-of-order execution of instructions.

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

Therefore Java defines a Memory Model, that states under which circumstances threads can see consistent state of the shared data.

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

In your particular case, adding volatile guarantees visibility.

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

#4


5  

They are "shared" of course in the sense that they both refer to the same variable, but they don't necessarily see each other's updates. This is true for any variable, not just static.

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

And in theory, writes made by another thread can appear to be in a different order, unless the variables are declared volatile or the writes are explicitly synchronized.

理论上,另一个线程所做的写操作可能看起来是不同的顺序,除非变量被声明为volatile或写操作是显式同步的。

#5


4  

Within a single classloader, static fields are always shared. To explicitly scope data to threads, you'd want to use a facility like ThreadLocal.

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

#6


1  

When you initialize static primitive type variable java default assigns a value for static variables

初始化静态基元类型变量时,java default为静态变量赋值

public static int i ;

when you define the variable like this the default value of i = 0; thats why there is a possibility to get you 0. then the main thread updates the value of boolean ready to true. since ready is a static variable , main thread and the other thread reference to the same memory address so the ready variable change. so the secondary thread get out from while loop and print value. when printing the value initialized value of number is 0. if the thread process has passed while loop before main thread update number variable. then there is a possibility to print 0

当你像这样定义变量时,默认值i = 0;这就是为什么有可能得到你0.然后主线程将布尔值ready更新为true。因为ready是一个静态变量,主线程和另一个线程引用相同的内存地址所以就绪变量。所以辅助线程从while循环和打印值中退出。当打印数值初始化值为0时,如果线程进程已经在主线程更新数字变量之前循环传递。然后有可能打印0

#7


-2  

@dontocsata you can go back to your teacher and school him a little :)

@dontocsata你可以回到你的老师和他一点点:)

few notes from the real world and regardless what you see or be told. Please NOTE, the words below are regarding this particular case in the exact order shown.

来自现实世界的一些笔记,无论你看到或被告知什么。请注意,下面的字样是按照所示的确切顺序与此特定情况有关。

The following 2 variable will reside on the same cache line under virtually any know architecture.

几乎任何已知的架构下,以下2个变量都将驻留在同一缓存行中。

private static boolean ready;  
private static int number;  

Thread.exit (main thread) is guaranteed to exit and exit is guaranteed to cause a memory fence, due to the thread group thread removal (and many other issues). (it's a synchronized call, and I see no single way to be implemented w/o the sync part since the ThreadGroup must terminate as well if no daemon threads are left, etc).

由于线程组线程的移除(以及许多其他问题),Thread.exit(主线程)保证退出并保证退出会导致内存栅栏。 (这是一个同步调用,我认为没有单一的方法可以实现同步部分,因为如果没有留下守护程序线程,ThreadGroup也必须终止,等等)。

The started thread ReaderThread is going to keep the process alive since it is not a daemon one! Thus ready and number will be flushed together (or the number before if a context switch occurs) and there is no real reason for reordering in this case at least I can't even think of one. You will need something truly weird to see anything but 42. Again I do presume both static variables will be in the same cache line. I just can't imagine a cache line 4 bytes long OR a JVM that will not assign them in a continuous area (cache line).

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

#1


57  

There is no visibility issue specific to static variables. There is a visibility issue imposed by the JVM's memory model. Here's an article talking about the memory model and how writes become visible to threads. You can't count on changes one thread makes becoming visible to other threads in a timely manner (actually the JVM has no obligation to make those changes visible to you at all), unless you establish a happens-before relationship, here's a quote from that link (supplied in the comment by Jed Wesley-Smith):

静态变量没有特定的可见性问题。 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:

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

  • 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.

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

#2


28  

He was talking about visibility and not to be taken too literally.

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

Static variables are indeed shared between threads, but the changes made in one thread may not be visible to another thread immediately, making it seem like there are two copies of the variable.

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

This article presents a view that is consistent with how he presented the info:

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

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:

首先,您必须了解Java内存模型的一些内容。多年来我一直在努力解释一下这个问题。截至今天,我能想到描述它的最佳方式是你是这样想象的:

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

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

  • 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.

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

...

...

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

But again, this is simply a mental model to think about threading and volatile, not literally how the JVM works.

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

#3


9  

Basically it's true, but actually the problem is more complex. Visibility of shared data can be affected not only by CPU caches, but also by out-of-order execution of instructions.

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

Therefore Java defines a Memory Model, that states under which circumstances threads can see consistent state of the shared data.

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

In your particular case, adding volatile guarantees visibility.

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

#4


5  

They are "shared" of course in the sense that they both refer to the same variable, but they don't necessarily see each other's updates. This is true for any variable, not just static.

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

And in theory, writes made by another thread can appear to be in a different order, unless the variables are declared volatile or the writes are explicitly synchronized.

理论上,另一个线程所做的写操作可能看起来是不同的顺序,除非变量被声明为volatile或写操作是显式同步的。

#5


4  

Within a single classloader, static fields are always shared. To explicitly scope data to threads, you'd want to use a facility like ThreadLocal.

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

#6


1  

When you initialize static primitive type variable java default assigns a value for static variables

初始化静态基元类型变量时,java default为静态变量赋值

public static int i ;

when you define the variable like this the default value of i = 0; thats why there is a possibility to get you 0. then the main thread updates the value of boolean ready to true. since ready is a static variable , main thread and the other thread reference to the same memory address so the ready variable change. so the secondary thread get out from while loop and print value. when printing the value initialized value of number is 0. if the thread process has passed while loop before main thread update number variable. then there is a possibility to print 0

当你像这样定义变量时,默认值i = 0;这就是为什么有可能得到你0.然后主线程将布尔值ready更新为true。因为ready是一个静态变量,主线程和另一个线程引用相同的内存地址所以就绪变量。所以辅助线程从while循环和打印值中退出。当打印数值初始化值为0时,如果线程进程已经在主线程更新数字变量之前循环传递。然后有可能打印0

#7


-2  

@dontocsata you can go back to your teacher and school him a little :)

@dontocsata你可以回到你的老师和他一点点:)

few notes from the real world and regardless what you see or be told. Please NOTE, the words below are regarding this particular case in the exact order shown.

来自现实世界的一些笔记,无论你看到或被告知什么。请注意,下面的字样是按照所示的确切顺序与此特定情况有关。

The following 2 variable will reside on the same cache line under virtually any know architecture.

几乎任何已知的架构下,以下2个变量都将驻留在同一缓存行中。

private static boolean ready;  
private static int number;  

Thread.exit (main thread) is guaranteed to exit and exit is guaranteed to cause a memory fence, due to the thread group thread removal (and many other issues). (it's a synchronized call, and I see no single way to be implemented w/o the sync part since the ThreadGroup must terminate as well if no daemon threads are left, etc).

由于线程组线程的移除(以及许多其他问题),Thread.exit(主线程)保证退出并保证退出会导致内存栅栏。 (这是一个同步调用,我认为没有单一的方法可以实现同步部分,因为如果没有留下守护程序线程,ThreadGroup也必须终止,等等)。

The started thread ReaderThread is going to keep the process alive since it is not a daemon one! Thus ready and number will be flushed together (or the number before if a context switch occurs) and there is no real reason for reordering in this case at least I can't even think of one. You will need something truly weird to see anything but 42. Again I do presume both static variables will be in the same cache line. I just can't imagine a cache line 4 bytes long OR a JVM that will not assign them in a continuous area (cache line).

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