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开始对其进行的改进。