“volatile”关键字用于什么?

时间:2022-11-26 10:48:09

I read some articles about the volatile keyword but I could not figure out its correct usage. Could you please tell me what it should be used for in C# and in Java?

我读了一些关于volatile关键字的文章,但是我不知道它的正确用法。你能告诉我在c#和Java中应该用什么吗?

7 个解决方案

#1


70  

For both C# and Java, "volatile" tells the compiler that the value of a variable must never be cached as its value may change outside of the scope of the program itself. The compiler will then avoid any optimisations that may result in problems if the variable changes "outside of its control".

对于c#和Java,“volatile”告诉编译器,一个变量的值决不能缓存,因为它的值可能在程序本身范围之外发生变化。然后,编译器将避免任何优化,如果变量“在其控制之外”发生更改,可能导致问题。

#2


132  

Consider this example:

考虑一下这个例子:

int i = 5;
System.out.println(i);

The compiler may optimize this to just print 5, like this:

编译器可以把它优化为只打印5,如下所示:

System.out.println(5);

However, if there is another thread which can change i, this is the wrong behaviour. If another thread changes i to be 6, the optimized version will still print 5.

然而,如果有另一个线程可以改变我,这就是错误的行为。如果另一个线程将i更改为6,那么优化后的版本仍将打印5。

The volatile keyword prevents such optimization and caching, and thus is useful when a variable can be changed by another thread.

volatile关键字可以防止这种优化和缓存,因此,当变量可以被另一个线程更改时,volatile将非常有用。

#3


31  

The volatile keyword has different meanings in both Java and C#.

volatile关键字在Java和c#中有不同的含义。

Java

From the Java Language Spec :

来自Java语言规范:

A field may be declared volatile, in which case the Java memory model ensures that all threads see a consistent value for the variable.

字段可以声明为volatile,在这种情况下,Java内存模型确保所有线程都能看到变量的一致值。

C#

From the C# Reference on the volatile keyword:

从c#引用的volatile关键字:

The volatile keyword indicates that a field can be modified in the program by something such as the operating system, the hardware, or a concurrently executing thread.

volatile关键字指示可以在程序中通过操作系统、硬件或并发执行的线程来修改字段。

#4


28  

To understand what volatile does to a variable, it's important to understand what happens when the variable is not volatile.

要了解volatile变量对变量的影响,重要的是要了解当变量不是volatile时发生的事情。

  • Variable is Non-volatile
  • 非易失性变量

When two threads A & B are accessing a non-volatile variable, each thread will maintain a local copy of the variable in it's local cache. Any changes done by thread A in it's local cache won't be visible to the thread B.

当两个线程A和B访问一个非易失性变量时,每个线程将在其本地缓存中维护该变量的本地副本。线程A在本地缓存中所做的任何更改对线程B来说都是不可见的。

  • Variable is volatile
  • 变量是不稳定的

When variables are declared volatile it essentially means that threads should not cache such a variable or in other words threads should not trust the values of these variables unless they are directly read from the main memory.

当变量被声明为volatile时,本质上意味着线程不应该缓存这样的变量,换句话说,线程不应该信任这些变量的值,除非它们直接从主内存读取。

So, when to make a variable volatile?

那么,什么时候让变量变得不稳定呢?

When you have a variable which can be accessed by many threads and you want every thread to get the latest updated value of that variable even if the value is updated by any other thread/process/outside of the program.

当您有一个可以被多个线程访问的变量,并且您希望每个线程获得该变量的最新更新值时,即使该值是由程序之外的任何其他线程/进程更新的。

#5


27  

Reads of volatile fields have acquire semantics. This means that it is guaranteed that the memory read from the volatile variable will occur before any following memory reads. It blocks the compiler from doing the reordering, and if the hardware requires it (weakly ordered CPU), it will use a special instruction to make the hardware flush any reads that occur after the volatile read but were speculatively started early, or the CPU could prevent them from being issued early in the first place, by preventing any speculative load from occurring between the issue of the load acquire and its retirement.

易失性字段的读取具有语义。这意味着可以保证,从volatile变量读取的内存将在任何后续内存读取之前发生。阻止编译器进行重新排序,如果硬件需要(弱下令CPU),它将使用一个特殊的指令,使硬件冲洗后发生的任何读取挥发性阅读,但大胆的开始早,或者CPU可以防止他们发行的早期首先,阻止任何投机负载之间发生负载获得及其退休的问题。

Writes of volatile fields have release semantics. This means that it is guaranteed that any memory writes to the volatile variable are guaranteed to be delayed until all previous memory writes are visible to other processors.

volatile字段的写入具有发布语义。这意味着要保证向volatile变量写入的任何内存都被延迟,直到其他处理器可以看到所有以前的内存写入。

Consider the following example:

考虑下面的例子:

something.foo = new Thing();

If foo is a member variable in a class, and other CPUs have access to the object instance referred to by something, they might see the value foo change before the memory writes in the Thing constructor are globally visible! This is what "weakly ordered memory" means. This could occur even if the compiler has all of the stores in the constructor before the store to foo. If foo is volatile then the store to foo will have release semantics, and the hardware guarantees that all of the writes before the write to foo are visible to other processors before allowing the write to foo to occur.

如果foo是类中的一个成员变量,而其他cpu可以访问某个引用的对象实例,那么在对象构造函数的内存写入全局可见之前,它们可能会看到foo的值更改!这就是“弱有序存储器”的意思。即使编译器在存储到foo之前的构造函数中拥有所有的存储,也可能发生这种情况。如果foo是可变的,那么foo的存储将具有发布语义,并且硬件保证在写入foo之前的所有写操作在允许写入foo之前对其他处理器是可见的。

How is it possible for the writes to foo to be reordered so badly? If the cache line holding foo is in the cache, and the stores in the constructor missed the cache, then it is possible for the store to complete much sooner than the writes to the cache misses.

对foo的写怎么可能被如此严重地重新排序?如果保存foo的缓存行在缓存中,而构造函数中的存储丢失了缓存,那么存储完成缓存的时间可能要比写入缓存的时间短得多。

The (awful) Itanium architecture from Intel had weakly ordered memory. The processor used in the original XBox 360 had weakly ordered memory. Many ARM processors, including the very popular ARMv7-A have weakly ordered memory.

来自英特尔(Intel)的(糟糕的)Itanium体系结构具有弱有序的内存。在最初的XBox 360中使用的处理器具有弱有序的内存。许多ARM处理器,包括非常流行的ARMv7-A都具有弱有序的内存。

Developers often don't see these data races because things like locks will do a full memory barrier, essentially the same thing as acquire and release semantics at the same time. No loads inside the lock can be speculatively executed before the lock is acquired, they are delayed until the lock is acquired. No stores can be delayed across a lock release, the instruction that releases the lock is delayed until all of the writes done inside the lock are globally visible.

开发人员通常不会看到这些数据竞争,因为像锁这样的东西会造成完全的内存障碍,本质上与同时获得和发布语义相同。在锁被获取之前,锁内的任何负载都不能被推测执行,它们被延迟到锁被获取。没有任何存储可以在锁发行版中被延迟,释放锁的指令被延迟,直到锁中完成的所有写操作都是全局可见的。

A more complete example is the "Double-checked locking" pattern. The purpose of this pattern is to avoid having to always acquire a lock in order to lazy initialize an object.

更完整的示例是“双重检查锁定”模式。此模式的目的是为了避免总是获取一个锁,以便惰性初始化一个对象。

Snagged from Wikipedia:

从*的:

public class MySingleton {
    private static object myLock = new object();
    private static volatile MySingleton mySingleton = null;

    private MySingleton() {
    }

    public static MySingleton GetInstance() {
        if (mySingleton == null) { // 1st check
            lock (myLock) {
                if (mySingleton == null) { // 2nd (double) check
                    mySingleton = new MySingleton();
                    // Write-release semantics are implicitly handled by marking mySingleton with
                    // 'volatile', which inserts the necessary memory barriers between the constructor call
                    // and the write to mySingleton. The barriers created by the lock are not sufficient
                    // because the object is made visible before the lock is released.
                }
            }
        }
        // The barriers created by the lock are not sufficient because not all threads will
        // acquire the lock. A fence for read-acquire semantics is needed between the test of mySingleton
        // (above) and the use of its contents.This fence is automatically inserted because mySingleton is
        // marked as 'volatile'.
        return mySingleton;
    }
}

In this example, the stores in the MySingleton constructor might not be visible to other processors before the store to mySingleton. If that happens, the other threads that peek at mySingleton will not acquire a lock and they will not necessarily pick up the writes to the constructor.

在本例中,MySingleton构造函数中的存储在存储到MySingleton之前可能对其他处理器不可见。如果发生这种情况,偷看单例mySingleton的其他线程将不会获得一个锁,它们也不一定会获取对构造函数的写操作。

volatile never prevents caching. What it does is guarantee the order in which other processors "see" writes. A store release will delay a store until all pending writes are complete and a bus cycle has been issued telling other processors to discard/writeback their cache line if they happen to have the relevant lines cached. A load acquire will flush any speculated reads, ensuring that they won't be stale values from the past.

挥发性从来没有阻止缓存。它所做的是保证其他处理器“看到”写入的顺序。一个商店的发布将会延迟一个存储,直到所有未决的写入完成,并且一个公共汽车周期已经被发布,告诉其他的处理器如果恰好有相关的行被缓存,就会丢弃/回写他们的高速缓存线。负载获取将刷新所有推测的读取,确保它们不会是过时的值。

#6


9  

In Java, "volatile" is used to tell the JVM that the variable may be used by multiple threads at the same time, so certain common optimizations cannot be applied.

在Java中,“volatile”用于告诉JVM该变量可以同时被多个线程使用,因此不能应用某些常见的优化。

Notably the situation where the two threads accessing the same variable are running on separate CPU's in the same machine. It is very common for CPU's to cache aggressively the data it holds because memory access is very much slower than cache access. This means that if the data is updated in CPU1 it must immediately go through all caches and to main memory instead of when the cache decides to clear itself, so that CPU2 can see the updated value (again by disregarding all caches on the way).

值得注意的是,访问相同变量的两个线程在同一台机器上的不同CPU上运行。CPU通常会主动缓存它所保存的数据,因为内存访问要比缓存访问慢得多。这意味着,如果数据在CPU1中被更新,它必须立即遍历所有缓存和主内存,而不是当缓存决定清除自己时,这样CPU2就可以看到更新后的值(同样,不考虑路上的所有缓存)。

#7


1  

When you are reading data that is non-volatile, the executing thread may or may not always get the updated value. But if the object is volatile, the thread always gets the most up-to-date value.

当您读取非易失性的数据时,执行的线程可能或可能不总是得到更新的值。但是,如果对象是可变的,那么线程总是获得最新的值。

#1


70  

For both C# and Java, "volatile" tells the compiler that the value of a variable must never be cached as its value may change outside of the scope of the program itself. The compiler will then avoid any optimisations that may result in problems if the variable changes "outside of its control".

对于c#和Java,“volatile”告诉编译器,一个变量的值决不能缓存,因为它的值可能在程序本身范围之外发生变化。然后,编译器将避免任何优化,如果变量“在其控制之外”发生更改,可能导致问题。

#2


132  

Consider this example:

考虑一下这个例子:

int i = 5;
System.out.println(i);

The compiler may optimize this to just print 5, like this:

编译器可以把它优化为只打印5,如下所示:

System.out.println(5);

However, if there is another thread which can change i, this is the wrong behaviour. If another thread changes i to be 6, the optimized version will still print 5.

然而,如果有另一个线程可以改变我,这就是错误的行为。如果另一个线程将i更改为6,那么优化后的版本仍将打印5。

The volatile keyword prevents such optimization and caching, and thus is useful when a variable can be changed by another thread.

volatile关键字可以防止这种优化和缓存,因此,当变量可以被另一个线程更改时,volatile将非常有用。

#3


31  

The volatile keyword has different meanings in both Java and C#.

volatile关键字在Java和c#中有不同的含义。

Java

From the Java Language Spec :

来自Java语言规范:

A field may be declared volatile, in which case the Java memory model ensures that all threads see a consistent value for the variable.

字段可以声明为volatile,在这种情况下,Java内存模型确保所有线程都能看到变量的一致值。

C#

From the C# Reference on the volatile keyword:

从c#引用的volatile关键字:

The volatile keyword indicates that a field can be modified in the program by something such as the operating system, the hardware, or a concurrently executing thread.

volatile关键字指示可以在程序中通过操作系统、硬件或并发执行的线程来修改字段。

#4


28  

To understand what volatile does to a variable, it's important to understand what happens when the variable is not volatile.

要了解volatile变量对变量的影响,重要的是要了解当变量不是volatile时发生的事情。

  • Variable is Non-volatile
  • 非易失性变量

When two threads A & B are accessing a non-volatile variable, each thread will maintain a local copy of the variable in it's local cache. Any changes done by thread A in it's local cache won't be visible to the thread B.

当两个线程A和B访问一个非易失性变量时,每个线程将在其本地缓存中维护该变量的本地副本。线程A在本地缓存中所做的任何更改对线程B来说都是不可见的。

  • Variable is volatile
  • 变量是不稳定的

When variables are declared volatile it essentially means that threads should not cache such a variable or in other words threads should not trust the values of these variables unless they are directly read from the main memory.

当变量被声明为volatile时,本质上意味着线程不应该缓存这样的变量,换句话说,线程不应该信任这些变量的值,除非它们直接从主内存读取。

So, when to make a variable volatile?

那么,什么时候让变量变得不稳定呢?

When you have a variable which can be accessed by many threads and you want every thread to get the latest updated value of that variable even if the value is updated by any other thread/process/outside of the program.

当您有一个可以被多个线程访问的变量,并且您希望每个线程获得该变量的最新更新值时,即使该值是由程序之外的任何其他线程/进程更新的。

#5


27  

Reads of volatile fields have acquire semantics. This means that it is guaranteed that the memory read from the volatile variable will occur before any following memory reads. It blocks the compiler from doing the reordering, and if the hardware requires it (weakly ordered CPU), it will use a special instruction to make the hardware flush any reads that occur after the volatile read but were speculatively started early, or the CPU could prevent them from being issued early in the first place, by preventing any speculative load from occurring between the issue of the load acquire and its retirement.

易失性字段的读取具有语义。这意味着可以保证,从volatile变量读取的内存将在任何后续内存读取之前发生。阻止编译器进行重新排序,如果硬件需要(弱下令CPU),它将使用一个特殊的指令,使硬件冲洗后发生的任何读取挥发性阅读,但大胆的开始早,或者CPU可以防止他们发行的早期首先,阻止任何投机负载之间发生负载获得及其退休的问题。

Writes of volatile fields have release semantics. This means that it is guaranteed that any memory writes to the volatile variable are guaranteed to be delayed until all previous memory writes are visible to other processors.

volatile字段的写入具有发布语义。这意味着要保证向volatile变量写入的任何内存都被延迟,直到其他处理器可以看到所有以前的内存写入。

Consider the following example:

考虑下面的例子:

something.foo = new Thing();

If foo is a member variable in a class, and other CPUs have access to the object instance referred to by something, they might see the value foo change before the memory writes in the Thing constructor are globally visible! This is what "weakly ordered memory" means. This could occur even if the compiler has all of the stores in the constructor before the store to foo. If foo is volatile then the store to foo will have release semantics, and the hardware guarantees that all of the writes before the write to foo are visible to other processors before allowing the write to foo to occur.

如果foo是类中的一个成员变量,而其他cpu可以访问某个引用的对象实例,那么在对象构造函数的内存写入全局可见之前,它们可能会看到foo的值更改!这就是“弱有序存储器”的意思。即使编译器在存储到foo之前的构造函数中拥有所有的存储,也可能发生这种情况。如果foo是可变的,那么foo的存储将具有发布语义,并且硬件保证在写入foo之前的所有写操作在允许写入foo之前对其他处理器是可见的。

How is it possible for the writes to foo to be reordered so badly? If the cache line holding foo is in the cache, and the stores in the constructor missed the cache, then it is possible for the store to complete much sooner than the writes to the cache misses.

对foo的写怎么可能被如此严重地重新排序?如果保存foo的缓存行在缓存中,而构造函数中的存储丢失了缓存,那么存储完成缓存的时间可能要比写入缓存的时间短得多。

The (awful) Itanium architecture from Intel had weakly ordered memory. The processor used in the original XBox 360 had weakly ordered memory. Many ARM processors, including the very popular ARMv7-A have weakly ordered memory.

来自英特尔(Intel)的(糟糕的)Itanium体系结构具有弱有序的内存。在最初的XBox 360中使用的处理器具有弱有序的内存。许多ARM处理器,包括非常流行的ARMv7-A都具有弱有序的内存。

Developers often don't see these data races because things like locks will do a full memory barrier, essentially the same thing as acquire and release semantics at the same time. No loads inside the lock can be speculatively executed before the lock is acquired, they are delayed until the lock is acquired. No stores can be delayed across a lock release, the instruction that releases the lock is delayed until all of the writes done inside the lock are globally visible.

开发人员通常不会看到这些数据竞争,因为像锁这样的东西会造成完全的内存障碍,本质上与同时获得和发布语义相同。在锁被获取之前,锁内的任何负载都不能被推测执行,它们被延迟到锁被获取。没有任何存储可以在锁发行版中被延迟,释放锁的指令被延迟,直到锁中完成的所有写操作都是全局可见的。

A more complete example is the "Double-checked locking" pattern. The purpose of this pattern is to avoid having to always acquire a lock in order to lazy initialize an object.

更完整的示例是“双重检查锁定”模式。此模式的目的是为了避免总是获取一个锁,以便惰性初始化一个对象。

Snagged from Wikipedia:

从*的:

public class MySingleton {
    private static object myLock = new object();
    private static volatile MySingleton mySingleton = null;

    private MySingleton() {
    }

    public static MySingleton GetInstance() {
        if (mySingleton == null) { // 1st check
            lock (myLock) {
                if (mySingleton == null) { // 2nd (double) check
                    mySingleton = new MySingleton();
                    // Write-release semantics are implicitly handled by marking mySingleton with
                    // 'volatile', which inserts the necessary memory barriers between the constructor call
                    // and the write to mySingleton. The barriers created by the lock are not sufficient
                    // because the object is made visible before the lock is released.
                }
            }
        }
        // The barriers created by the lock are not sufficient because not all threads will
        // acquire the lock. A fence for read-acquire semantics is needed between the test of mySingleton
        // (above) and the use of its contents.This fence is automatically inserted because mySingleton is
        // marked as 'volatile'.
        return mySingleton;
    }
}

In this example, the stores in the MySingleton constructor might not be visible to other processors before the store to mySingleton. If that happens, the other threads that peek at mySingleton will not acquire a lock and they will not necessarily pick up the writes to the constructor.

在本例中,MySingleton构造函数中的存储在存储到MySingleton之前可能对其他处理器不可见。如果发生这种情况,偷看单例mySingleton的其他线程将不会获得一个锁,它们也不一定会获取对构造函数的写操作。

volatile never prevents caching. What it does is guarantee the order in which other processors "see" writes. A store release will delay a store until all pending writes are complete and a bus cycle has been issued telling other processors to discard/writeback their cache line if they happen to have the relevant lines cached. A load acquire will flush any speculated reads, ensuring that they won't be stale values from the past.

挥发性从来没有阻止缓存。它所做的是保证其他处理器“看到”写入的顺序。一个商店的发布将会延迟一个存储,直到所有未决的写入完成,并且一个公共汽车周期已经被发布,告诉其他的处理器如果恰好有相关的行被缓存,就会丢弃/回写他们的高速缓存线。负载获取将刷新所有推测的读取,确保它们不会是过时的值。

#6


9  

In Java, "volatile" is used to tell the JVM that the variable may be used by multiple threads at the same time, so certain common optimizations cannot be applied.

在Java中,“volatile”用于告诉JVM该变量可以同时被多个线程使用,因此不能应用某些常见的优化。

Notably the situation where the two threads accessing the same variable are running on separate CPU's in the same machine. It is very common for CPU's to cache aggressively the data it holds because memory access is very much slower than cache access. This means that if the data is updated in CPU1 it must immediately go through all caches and to main memory instead of when the cache decides to clear itself, so that CPU2 can see the updated value (again by disregarding all caches on the way).

值得注意的是,访问相同变量的两个线程在同一台机器上的不同CPU上运行。CPU通常会主动缓存它所保存的数据,因为内存访问要比缓存访问慢得多。这意味着,如果数据在CPU1中被更新,它必须立即遍历所有缓存和主内存,而不是当缓存决定清除自己时,这样CPU2就可以看到更新后的值(同样,不考虑路上的所有缓存)。

#7


1  

When you are reading data that is non-volatile, the executing thread may or may not always get the updated value. But if the object is volatile, the thread always gets the most up-to-date value.

当您读取非易失性的数据时,执行的线程可能或可能不总是得到更新的值。但是,如果对象是可变的,那么线程总是获得最新的值。