当声明共享变量为volatile后,对这个变量的读/写将会很特别。那么它到底起着怎样的作用呢?
前言:
volatile是Java提供的一种轻量级的同步机制。Java语言包含俩种内在的同步机制:同步块(或同步方法)和volatile变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。
1.JMM的三大特征
JAVA内存模型的三个特征:
1.原子性:
一个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态。
2.可见性:
可见性指的是线程之间的可见性,一个线程修改的状态对另外一个线程是可见的。也就是一个线程对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。 其实之所以要保证可见性,主要与Java的内存模型有关。
内存模型 每个线程在执行的时候,会从主内存中拷贝一份到自己的本地内存,线程操作的时候是操作自己本地内存的变量,这样每个线程都操作自己本地内存的变量,就可能导致这个共享变量的数据不一致,这就体现了volatile的作用了。volatile可以使得一个线程操作共享变量的时候,能读到这个共享变量最新的变化,在这个变化上操作。
示意图
线程对共享变量的所有操作都必须在??的本地内存中进?,不能直接从主内存中读取。JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可?性保证。
3.有序性:
对于一个线程的代码而言,我们总是以为代码的执行是从前往后的,依次执行的。这么说不能说完全不对,在单线程程序里,确实会这样执行;但是在多线程并发时,程序的执行就有可能出现乱序。用一句话可以总结为:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。
2.volatile关键字的特性:
? ? ? Java提供了两个关键字volatile和synchronized来保证多线程之间操作的有序性,volatile关键字本身通过加入内存屏障来禁止指令的重排序,而synchronized关键字通过一个变量在同一时间只允许有一个线程对其进行加锁的规则来实现,
当一个变量被volatile修饰时,会拥有两个特性:
- 保证了不同线程对该变量操作的内存可见性.(当一个线程修改了变量,其他使用次变量的线程可以立即知道这一修改).
- 禁止了指令重排序.
1.可见性
2.禁止指令重排序优化。 指令重排序是什么?简单点说就是jvm会把代码中没有依赖赋值的地方打乱执行顺序,由于一些规则限定,我们在单线程内观察不到打乱的现象(线程内表现为串行的语义),但是在并发程序中,从别的线程看另一个线程,操作是无序的。
volatile只可以用来修饰变量,不可以修饰方法和类:
一个非常经典的指令重排序例子:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这是很经典的双重锁校验实现的单例模式,想必很多人都看到过,代码中可能会被多个线程访问的singleton 变量使用volatile修饰.我们看到singleton 用了volatile修饰,由于 singleton = new SingletonTest();可分解为:
1.memory =allocate(); //分配对象的内存空间 2.ctorInstance(memory); //初始化对象 3.singleton =memory; //设置singleton 指向刚分配的内存地址
操作2依赖1,但是操作3不依赖2,所以有可能出现1,3,2的顺序,当出现这种顺序的时候,虽然instance不为空,但是对象也有可能没有正确初始化,会出错
可参考:JMM概述_牧竹子的博客-CSDN博客_jmm
|