翻译自geeksforgeeks
使用volatile是保证线程安全的另一种方式(如同步,原子包装)。线程安全意味着多个线程可以同时使用方法或类实例而不会出现任何问题。
考虑下面的简单的例子:
class SharedObj
{
// Changes made to sharedVar in one thread
// may not immediately reflect in other thread
static int sharedVar = 6;
}
假设两个线程正在使用SharedObj
。如果两个线程在不同的处理器上运行,则每个线程可以拥有自己的sharedVariable
本地副本。如果一个线程修改了它的值,则该更改可能不会立即反映在主存储器中的原始值中。这取决于缓存的写策略。现在另一个线程由于不知道这次修改,导致了数据不一致。
下图显示,如果两个线程在不同的处理器上运行,则sharedVariable
的值在不同的线程中可能不同。
注意,在没有任何同步操作的情况下写入正常变量可能对任何读取线程都不可见(此行为称为顺序一致性)。虽然大多数现代硬件提供了良好的高速缓存一致性,因此很可能一个高速缓存中的修改会反映在其他高速缓存中,但依靠硬件来“修复”错误的应用程序并不是一个好的做法。
class SharedObj
{
// volatile keyword here makes sure that
// the changes made in one thread are
// immediately reflect in other thread
static volatile int sharedVar = 6;
}
注意,不应将volatile
与static
修饰符混淆。静态变量是在所有对象之间共享的类成员。主内存中只有一个副本。
volatile vs synchronized:
在我们继续之前,让我们来看看锁和同步的两个重要特性。
- 相互排斥:这意味着一次只有一个线程或进程可以执行一段代码(临界区)。
- 可见性:这意味着一个线程对共享数据所做的更改对其他线程可见。
Java的synchronized
关键字保证了互斥和可见性。如果我们使修改共享变量值的代码块同步,则只有一个线程可以进入该块,并且它所做的更改将反映在主内存中。同时刻所有其他尝试进入该块的线程将被阻止并进入休眠状态。
在某些情况下,我们可能只需要可见性而非原子性。在这种情况下使用synchronized
是一种矫枉过正(overkill)并且可能导致可伸缩性问题。这时volatile
关键字就派上用场了。volatile
关键字具有synchronized
的可见性但没有原子性。volatile
变量的值永远不会被缓存,所有的写入和读取都将在主内存中完成。然而,volatile
的使用仅局限于有限的一些情况,因为大多数时候需要原子性。比如,一个简单的递增语句,x = x + 1
;或者x++
,似乎是一个单独的操作,但实际上是必须以原子方式执行的复合的读取-修改-写入操作序列。
volatile :Java vs C/C++:
Java中的volatile
关键字不同于C或C++。对于Java,volatile
告诉编译器永远不要缓存变量的值,因为它的值可能会在程序本身的范围之外发生变化。在C / C ++中,开发嵌入式系统或设备驱动程序时需要“volatile”来读取或写入内存映射的硬件设备。特定设备寄存器的内容可能随时更改,因此需要volatile
关键字以确保编译器不会优化此类访问。