volatile
性质:
volatile是jvm提供的轻量的同步机制,其遵守可见性,有序性(禁止指令重排),但是并不遵守原子性,主要是这三个特性。
可见性:
根据JMM(Java内存模型 Java Memory Model)定义为主内存为工作内存,我们定义的变量存储在主内存中,线程操作的时候是在工作内存中进行操作的,而在工作内存中进行操作的变量都是从主内存进行拷贝过来的,操作完以后再写回主内存中。而这里的可见性就只指当多个线程操作同一个变量X的时候,当其中一个线程对变量X进行修改后写入主内存中,变量X的改变对其他线程可见。
比如上面两个线程A,B,从主内存中拷贝X到自己的工作内存当中,此时X的初始值为0,当线程A修改为1以后写入到主内存中,而线程B在读取变量 X的时候会读取到最新的值,这就是可见性。
非原子性:
上面的可见性介绍以后,可能会产生一个误区就是,以为volatile是线程安全,然而并不是线程安全,也就是volatile不遵守原子性。刚刚上面介绍中,操作才工作内存当中进行然后再写回主内存,然而这里就有一个问题,如果有多个线程操作同一变量,比如线程A读取到X的值为0,已经读取到自己的 工作内存当中,此时线程B也读取变量X到自己的工作内存,因为线程A还没有执行完,还没有写入到主内存当中,所以线程B读取到的值也是0,此时线程B将X改为1,并且写回主内存。此时有一个问题,线程A已经完成读取操作了,所以线程A中的X还是0。所以volatile并不能保证原子性。
有序性:
volatile能够保持有序性是因为禁止指令重排,而什么又是指令重排呢?
指令重排,这样说可能有点陌生,这是JVM调优的一种机制,在不存在数据依赖的情况下编译指令结果并不一定是按照代码顺序的。
class VolatileMode{
private String vlue;
private boolean flag=false;
public void init(){
new Thread(()->{
vlue="test"; // 1
flag=true; // 2
},"thread A").start();
new Thread(()->{
while (!flag){
try
{
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
//其他业务 doOther(vlue)
},"thread B").start();
}
}
比如上面线程 A中,理想状态下执行步骤应该是1>2,但是1,2步骤就不存在数据依赖,jvm优化后的编译有可能是2>1,先执行步骤2,然后此时我们再看线程B,如果先执行步骤2的话,线程B就跳出循环执行其他业务,而此时vlue并没有进行初始化,所以doOther(vlue)方法就很有可能出错。至于什么是数据依赖呢,就是不存在引用其他未初始化的变量,举个例子:
int x=0; // 1
int y=0; // 2
x=1; // 3
y=x; // 4
在第四步的时候就存在数据依赖,所以执行顺序就只会存在1>2>3>4,2>1>3>4,1>3>2>4,并不会出现类似2>4>1>3这种,因为在第四步的时候X还不存在。
总结:
volatile虽然是java提供的轻量同步机制,但是我们在并发包中可以经常看见volatile,为的就是其可见性与有序性。
如有问题欢迎指出!