我读了一些关于 volatile
关键字的文章,但是我无法弄清楚它的正确用法。 请告诉我在 C#和 Java 中应该如何使用它?
考虑以下示例:
int i = 5;
System.out.println(i);
编译器可能对此进行优化,仅打印5,就像这样:System.out.println(5);
然而,如果存在另一个线程可以改变i
,那么这是错误的行为。如果另一个线程将i
更改为6,则优化版本仍将打印5。
volatile
关键字可以防止这种优化和缓存,因此在变量可能被另一个线程更改时很有用。
i
标记为volatile
,优化仍然是有效的。在Java中,一切都与* happens-before *关系有关。 - Tom Hawtin - tacklinei
是一个局部变量,其他线程无论如何也不能更改它。如果它是一个字段,除非它是final
,否则编译器无法优化调用。我认为编译器不能基于假设一个字段“看起来”像final
而进行优化,除非它被明确地声明为这样。 - polygenelubricants在C#和Java中,“volatile”告诉编译器变量的值永远不会缓存,因为其值可能会在程序本身范围之外发生更改。然后,编译器将避免任何可能导致问题的优化,如果变量在“其控制范围之外”更改。
读取volatile字段具有获取语义。这意味着保证从volatile变量中读取的内存将发生在后续任何内存读取之前。它阻止编译器进行重新排序,并且如果硬件需要(弱序CPU),它将使用特殊指令使硬件刷新在volatile读取之后但早期被推测启动的任何读取,或者CPU可以通过防止在发出负载获取和其退役之间发生任何推测负载来防止它们提前发出。
写入volatile字段具有释放语义。这意味着保证对volatile变量的任何内存写入都会延迟,直到所有先前的内存写入对其他处理器可见。
考虑以下示例:
something.foo = new Thing();
如果foo
是类中的成员变量,并且其他CPU可以访问由something
引用的对象实例,则它们可能在Thing
构造函数中的内存写入全局可见之前看到值foo
发生改变!这就是“弱排序内存”的含义。即使编译器在将所有存储放入构造函数之前都有foo
的存储,也可能会发生这种情况。如果foo
是volatile
,则对foo
的存储将具有释放语义,并且硬件保证在允许对foo
进行写入之前,所有写入foo
之前的写入都可见于其他处理器。foo
的写入被重新排序得如此糟糕?如果持有foo
的缓存行在缓存中,并且构造函数中的存储未命中缓存,则可能导致存储比缓存未命中的写入完成得早得多。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
构造函数内的存储可能在 store 到 mySingleton
之前对其他处理器不可见。如果发生这种情况,窥视 mySingleton 的其他线程将无法获取锁定,并且他们不一定会获得构造函数的写入。
volatile
永远不会阻止缓存。它所做的是保证其他处理器“看到”的写入顺序。一个 store release 将延迟 store 直到所有挂起的写操作完成,并发出总线周期告诉其他处理器丢弃/写回他们可能已经缓存的相关行。load acquire 将刷新任何推测读取,确保它们不会是过去的旧值。
head
和tail
都需要是易失性的,以防止生产者假定tail
不会改变,并防止消费者假定head
不会改变。此外,必须使head
成为易失性以确保队列数据写入在将head
存储全局可见之前全局可见。 - doug65536volatile
关键字。 - tymtamVolatile关键字在Java和C#中有不同的含义。
如果一个字段被声明为volatile,那么Java内存模型确保所有线程看到该变量具有一致的值。
来自C# Reference(2021年3月31日检索):
Volatile关键字指示一个字段可能会被多个同时执行的线程修改。编译器、运行时系统甚至硬件可能为了提高性能而重新排列对内存位置的读写。声明为volatile的字段不受这些优化的影响。(...)
当您读取非易失性数据时,执行线程可能会或可能不会始终获取更新后的值。 但如果对象是易失性的,则线程始终获取最新的值。
Volatile关键字解决了并发问题,使得值同步。这个关键字主要用于线程中,当多个线程更新同一个变量时。