volatile:是java虚拟机提供的轻量级同步机制。
volatile三大特性:保证可见性、不保证原子性、禁止指令重排。
JMM三大特性:可见性、原子性、有序性。
JMM(Java Memory Mode java内存模型):由于jvm运行程序的实体是线程,而每个线程创建时jvm都会为其创建工作内存(有些地方称为栈内存),工作空间是每个线程私有的数据区域,而java内存模型规定所有的变量都保存在主物理内存中,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读写赋值等)都要在工作内存中进行,首先要将变量从主内存中拷贝到工作内存,然后对变量进行操作,操作完成后再把变量写回到主内存中,不能直接操作主内存中的变量。不同线程不能访问对方工作内存中的变量,线程中的通信(传值)必须通过主物理内存。
可见性:每个线程从主物理内存里读取变量age:16,t1线程对age变量进行修改,然后将修改结果age:18返回主物理内存,然后修改后的消息第一时间分享给其他线程的机制称为JMM(java内存模型)的可见性。
不保证原子性的原因:例如多个线程进行n++操作,t1线程读取主物理内存中的值为0,然后进行加一操作后为1,接着写回到主物理内存的过程中线程被挂起了,t2线程也进行同样的操作后主物理内存中的值为1,此时t1线程继续将1写到主物理内存,此时发生写数据丢失。
指令重排:计算机在执行程序时,编译器和处理器为了提高效率常常会对指令进行重排,一般分为三步:
源代码–>编译器优化指令的重排–>并行指令的重排–>内存系统的指令重排–>最终的执行指令
单线程环境确保优化指令重排的执行结果和顺序执行代码结果一致;
处理器在执行重排时要保证指令之间的依赖性;
多线程环境下线程交替执行,由于编译器优化指令重排的原因,线程之间使用的同一变量的一致性无法保证,结果无法预测。
public mySort(){
int x=1; //语句1
int y=2; //语句2
x=x+1; //语句3
y=y+1; //语句4
}
执行顺序有1234、2134、1324、2413
volatile禁止指令重排的原因举例:
class ResortDemo{
int i=0;
boolean b=flase;
public method1(){
i=1; //语句1
b=true; //语句2
}
public method2(){
if(b){
i+=5; //语句3
System.out.println(i);
}
}
}
由于多线程环境中存在指令重排,语句2可能会排在语句1的前面,从而输出5,为了消除这种可能我们在定义b变量时在前面使用volatile修饰。
volatile实现禁止指令重排,从而避免多线程环境下程序出现乱序执行的情况。
内存屏障(Memory Barrier),又叫内存栅栏,是一个cpu指令,它的作用有两个:
1.保证特定操作的执行顺序
2.保证某些内存的可见性(利用该特征实现volatile内存可见性)
由于编译器和处理器都能执行指令优化重排,如果在指令间插入一条内存屏障则会告诉编译器和cpu,不管什么指令都不能和这条内存屏障重排序,也就是说通过插入内存屏障可以禁止在屏障前后指令重排序优化。内存屏障另一个作用是强制刷出各种cpu的缓存数据,从而在任何cpu线程上都可以读到这些数据的最新版本。