1.概述
在这篇快速文章中,我们将关注Java语言中的最基本但经常被误解的概念 - volatile关键字。
在Java中,每个线程都有一个独立的内存空间,称为工作内存; 它保存了用于执行操作的不同变量的值。在执行操作之后,线程将变量的更新值复制到主存储器,这样其他线程可以从那里读取最新值。
简单地说,volatile关键字标记一个变量,在多个线程访问它的情况下,总是转到主内存,读取和写入。
2.何时使用volatile
在变量的下一个值依赖于先前值的情况下,由于读取和写入主存储器之间的时间间隔,读取和写入变量的多个线程可能会不同步。 。
这可以通过一个简单的例子来说明:
public class SharedObject {
private volatile int count = 0;
public void increamentCount() {
count++;
}
public int getCount() {
return count;
}
}
在此处没有同步,可能发生典型的竞争条件。基本上,在递增和写入主内存之间存在执行差距,其他线程可能会看到值0,并尝试将其写入主内存。
当然也可以通过使用Java提供的原子数据类型(如AtomicInt或AtomicLong)来避免竞争条件。
3.volatile和线程同步
对于所有多线程应用程序,我们需要确保行为一致的规则:
- 相互排斥 - 一次只有一个线程执行一个关键部分
- 可见性 - 一个线程对共享数据所做的更改对其他线程可见,以维护数据一致性
同步方法和块提供上述两种属性,但代价是牺牲应用程序的性能。
volatile是一个非常有用的关键字,因为它可以帮助确保数据变化的可见性方面,当然,不提供互斥。因此,在多线程并行执行代码块但需要确保可见性的情况下,它非常有用。
4.Happens-Before
从Java 5开始,volatile关键字还提供了额外的功能,可确保包括非volatile变量在内的所有变量的值与Volatile写操作一起写入主存储器。
这称为Happens-Before,因为它为所有变量提供了对另一个读取线程的可见性。此外,JVM不会重新排序volatile变量的读写指令。
我们来看看这个例子:
Thread 1
object.aNonValitileVariable = 1;
object.aVolatileVariable = 100; // volatile write
Thread 2:
int aNonValitileVariable = object.aNonValitileVariable;
int aVolatileVariable = object.aVolatileVariable;
在这种情况下,当Thread 1写入aVolatileVariable的值时,aNonValitileVariable的值也会写入主存储器。即使它不是一个volatile的变量,它也表现出一种不稳定的行为。
通过使用这些语义,我们可以将类中的一些变量定义为volatile并优化可见性保证。
5.结论
在本教程中,我们探讨了有关volatile关键字及其功能的更多信息,以及从Java 5开始对其进行的改进。