首先,volatile是什么?他是Java提供的一个内置的关键字。被此关键字修饰的变量有两种特性
请注意,JMM内存结构并不是指JVM虚拟的内存结构,及堆栈等。
(画的有点丑,将就看一下)
每次线程读取数据的时候,并不是直接去主内存中读取,会先在工作内存中去查找,变量是否存在,存在的话就直接使用,如果不存在才会去主内存中查找。在Java的内存模型中定义了一下八种操作来完成变量从主存拷贝到工作内存,从工作内存写回到主存。
Java虚拟机对volatile关键字有一些特殊的规则。
现代的操作系统为了对程序的运行进行优化,会在不改变程序运行的结果下,改变线程内部代码的执行顺序。例如以下代码
int i = 1; int j = 2; int z = 3; int x = i + j; 复制代码
在类似于这种情况下,如果根据程序代码的顺序,i会先存储,然后再读取,在进行运算。指令重排序后,可能会是i和j在z之后定义,这样可以减去一次加载变量的过程,会在一定程度上提高效率(当然,这只是一个简单的例子,还有很多更复杂的情况)
为什么需要这么一个关键字呢?当使用多线程的时候,难免会出现线程安全问题,即A线程修改的变量的值,B线程中使用该变量的时候,获取到的值并不是最新的,导致B线程处理的数据成为脏数据,导致一些未知的情况。
可能有的朋友会说为什么不使用锁呢。可以说关键字volatile是Java虚拟机提供的最轻量的一个同步机制相对于synchronized而言,是更轻一点的锁。synchronized在只有一个线程的时候,是轻量级锁,当出现多个线程同时竞争的时候,会变成重量级锁,会消耗较多的系统资源。
我们以一个单例模式的代码来举例说明。
public class Singleton { private volatile static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } public static void main(String[] args) { Singleton.getInstance(); } } 复制代码
这是一个很简单的懒汉式单例模式。当多个线程同时去获取的时候,只有一个线程能够创建这个对象。如果我们不添加volatile,则在线程创建好一个对象,释放锁之后,同时在等待获取锁的对象就会获取锁,然后判断,发现已经创建,然后再释放,其他竞争线程也是类似的。(这里的竞争线程是指与创建对象的那个线程同时竞争锁的线程,由于获取锁失败,会等待)。如果添加了volatile,会在使用的时候重新去加载这个对象,发现已经初始化,则放弃获取锁,减少了锁的竞争。
在学习了Java的内存模型之后,我们发现其实Java内存模型就是在围绕着并发过程中如何处理原子性、可见性和有序性。那volatile都可以保证这些特性吗?
volatile int i = 1; i++; 复制代码
对于这个来讲,是很常见的一个例子,由于i++他不是一个原子性操作,他会先读取,然后再运算,最后赋值。如果他想要实现原子性操作的话,需要借助于其他的锁机制。