我阅读了一些关于volatile
关键字的文章,但我无法弄清楚它的正确用法。你能告诉我在C#和Java中应该使用它吗?
答案 0 :(得分:151)
考虑这个例子:
int i = 5;
System.out.println(i);
编译器可以优化它以打印5,如下所示:
System.out.println(5);
但是,如果有另一个可以更改i
的线程,则这是错误的行为。如果另一个线程将i
更改为6,则优化版本仍将打印5。
volatile
关键字阻止了这种优化和缓存,因此当另一个线程可以更改变量时非常有用。
答案 1 :(得分:80)
对于C#和Java,“volatile”告诉编译器必须永远不要缓存变量的值,因为它的值可能会在程序本身的范围之外发生变化。然后,如果变量“在其控制范围之外”,则编译器将避免可能导致问题的任何优化。
答案 2 :(得分:35)
要理解volatile对变量的作用,了解变量不易变时会发生什么很重要。
当两个线程A& B正在访问非易失性变量,每个线程将在其本地缓存中维护变量的本地副本。线程A在其本地缓存中完成的任何更改都不会对线程B可见。
当变量被声明为volatile时,它实质上意味着线程不应该缓存这样的变量,换句话说,线程不应该信任这些变量的值,除非它们直接从主存储器读取。
那么,何时使变量变为volatile?
如果有一个变量可以被许多线程访问,并且您希望每个线程获得该变量的最新更新值,即使该值由程序中的任何其他线程/进程/更新。
答案 3 :(得分:33)
volatile keyword在Java和C#中有不同的含义。
字段可以声明为volatile,在这种情况下,Java内存模型可确保所有线程都看到变量的一致值。
来自volatile keyword上的C#参考:
volatile关键字表示可以通过操作系统,硬件或同时执行的线程在程序中修改字段。
答案 4 :(得分:29)
读取易失性字段获取语义。这意味着可以保证从volatile变量读取的内存将在任何后续内存读取之前发生。它阻止编译器进行重新排序,如果硬件需要它(弱排序的CPU),它将使用特殊指令使硬件刷新在易失性读取之后发生的任何读取但是推测性地提前启动,或者CPU可以通过防止在负荷获得和退休之间发生任何投机负荷,防止它们首先发布。
volatile字段的写入具有发布语义。这意味着可以保证对volatile变量的任何内存写入都会被延迟,直到所有先前的内存写入对其他处理器都可见。
考虑以下示例:
something.foo = new Thing();
如果foo
是类中的成员变量,并且其他CPU可以访问something
引用的对象实例,则他们可能会看到值foo
更改之前 Thing
构造函数中的内存写入全局可见!这就是"弱有序的内存"手段。即使编译器在存储到foo
之前在构造函数中具有所有存储,也可能发生这种情况。如果foo
为volatile
,则foo
的存储将具有释放语义,并且硬件保证在写入foo
之前所有写入对其他处理器可见允许写入foo
。
如何对foo
的写入进行如此糟糕的重新排序?如果保存foo
的缓存行位于缓存中,并且构造函数中的存储错过了缓存,那么存储可能比缓存写入错过的时间更早完成。
来自英特尔的(糟糕的)安腾架构的内存微弱。原始XBox 360中使用的处理器具有弱序存储器。许多ARM处理器,包括非常流行的ARMv7-A,内存都很弱。
开发人员通常不会看到这些数据争用,因为像锁这样的东西会完全占用内存,与获取和释放语义同时基本相同。在获取锁之前,可以推测性地执行锁内的任何负载,它们被延迟直到获得锁。锁定释放期间不会延迟存储,释放锁定的指令会被延迟,直到锁定内部完成的所有写入全局可见。
更完整的例子是"双重检查锁定"图案。此模式的目的是避免必须始终获取锁以便延迟初始化对象。
来自维基百科:
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;
}
}
在此示例中,MySingleton
构造函数中的商店在存储到mySingleton
之前可能对其他处理器不可见。如果发生这种情况,查看mySingleton的其他线程将不会获得锁定,并且它们不一定会获取对构造函数的写入。
volatile
永远不会阻止缓存。它的作用是保证其他处理器的顺序"见"写道。商店发布将延迟商店,直到所有待处理的写入完成并且已发出总线周期,告知其他处理器如果碰巧有相关的行被缓存则丢弃/回写其高速缓存行。负载获取将刷新任何推测读数,确保它们不会成为过去的陈旧值。
答案 5 :(得分:9)
在Java中,“volatile”用于告诉JVM多个线程可以同时使用该变量,因此无法应用某些常见的优化。
值得注意的是访问同一变量的两个线程在同一台机器上的不同CPU上运行的情况。由于内存访问速度比缓存访问速度慢得多,因此CPU会积极缓存它所拥有的数据是很常见的。这意味着如果数据在CPU1中更新,它必须立即通过所有缓存和主存储器,而不是在缓存决定清除自身时,以便CPU2可以看到更新的值(再次忽略所有缓存都在路上。)
答案 6 :(得分:1)
当您读取非易失性数据时,执行线程可能会或可能不会始终获取更新的值。 但是如果对象是易失性的,那么线程总是获得最新的值。
答案 7 :(得分:0)
Volatile正在解决并发问题。使该值同步。此关键字主要在线程中使用。当多个线程更新相同的变量时。