📜  Java中的volatile关键字

📅  最后修改于: 2020-03-29 07:35:44             🧑  作者: Mango

使用volatile是使类线程安全的另一种方式(如同步的原子包装)。线程安全意味着一个方法或类实例可以被多个线程同时使用而不会出现任何问题。
考虑下面的简单示例。

class SharedObj
{
   // 在一个线程内,改变sharedVar
   // 不会影响到其他线程
   static int sharedVar = 6;
}

假设两个线程正在SharedObj上工作。如果两个线程在不同的处理器上运行,则每个线程可能具有自己的sharedVariable本地副本。如果一个线程修改其值,则更改可能不会立即反映在主内存中的原始线程中。这取决于缓存的写入策略。现在,另一个线程不知道导致数据不一致的修改值。
下图显示,如果两个线程在不同的处理器上运行,则sharedVariable的值在不同的线程中可能会不同。

请注意,没有任何同步操作的普通变量的写入对于任何读取线程可能都不可见(此行为称为顺序一致性)。尽管大多数现代硬件都提供了良好的缓存一致性,所以很可能一个缓存中的更改会反映在其他缓存中,但是依靠硬件来“修复”有故障的应用程序不是一个好习惯。

class SharedObj
{
   // volatile关键字保证在一个线程的更改,可以立即反映到其他线程中
   static volatile int sharedVar = 6;
}

注意,volatile不应与static修饰符混淆。static静态变量是在所有对象之间共享的类成员。主存储器中只有一个副本。
volatile与synced:
在继续之前,让我们看一下锁和同步的两个重要功能。

  1. 互斥:这意味着一次只能有一个线程或进程执行一个代码块。
  2. 可见性:这意味着一个线程对共享数据所做的更改对其他线程可见。

Java的synced关键字可确保相互排斥和可见性。如果我们使修改共享变量值的线程块同步,则只有一个线程可以进入该块,并且其所做的更改将反映在主内存中。试图同时进入该块的所有其他线程将被阻塞并进入睡眠状态。
在某些情况下,我们可能只希望可见性而不是原子性。在这种情况下使用同步是不合适的,可能会导致可伸缩性问题。在这里,volatile可以来帮忙。volatile变量具有同步的可见性特征,但不具有原子性特征。volatile变量的值将永远不会被缓存,所有读写操作都将在主内存中进行。但是,由于大多数时候需要原子性,因此volatile的使用仅限于非常有限的一组情况。例如,一个简单的增量语句,例如x = x + 1; 或x ++似乎是单个操作,但实际上是必须原子执行的复合的读取-修改-写入操作序列。

Java与C/C++中的volatile
Java中的volatile与C/C++中的
“ volatile”限定符不同。对于Java,“volatile“告诉编译器,永远不要缓存变量的值,因为变量的值可能会在程序本身范围之外更改。在C/C++中,开发嵌入式系统或设备驱动程序时需要“volatile“,在其中您需要读取或写入内存映射的硬件设备。特定设备寄存器的内容可以随时更改,因此您需要使用“ volatile”关键字来确保编译器不会对此类访问进行优化。

参考:

https ://www.ibm.com/developerworks/Java/library/j-jtp06197/
https://docs.oracle.com/Javase/tutorial/essential/concurrency/atomic.html
http://tutorials.jenkov .com / Java-concurrency / volatile.html
https://pveentjer.wordpress.com/2008/05/17/jmm-thank-god-or-the-devil-for-strong-cache-coherence/