JAVA关键字Volatile
Volatile是在java面试中出现频率很高的一个技术点,说到Volatile不得不先说一下JVM的内存模型,java的内存模型分为主内存和工作内存,
主内存:
所有的变量都存储在主内存(不包括局部变量和,方法参数,他们是线程私有的)
工作内存:
jvm为每个线程分配的内存空间,工作内存保存了被该内存使用的变量的的主内存副本,
线程对变量的所有操作(读取、赋值等)都必需在工作内存中进行,而不能直接读写主内存中的变量,不同的线程之间也无法直接访问对方工作内存中的变量,线程之间变量值的传递均需要通过主内存如下图
工作内存中变量变化的过程
read:从主内存读取,发生在主内存
load:加载到工作内存,发生在工作内存
use:使用,发生在工作内存
assign:当前线程修改变量
store:将变量存储在工作内存
write:将新的值写到主内存
可以看到在这样的过程中,如果有多个线程同时修改共享变量做修改的话是会发生线程不安全的,我们写一段代码来看看是不是线程不安全
int index = 0;
@Test
public void unSaveUpadate() throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1L);
index++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(1000L);
System.out.println("输出值:" + index);
你会发现最后输出的数据始终是小于1000,造成结果不是预期的原因就是因为多个线程并发修改相同的变量时发生了覆盖的现象,举例:
线程1:从主内存中读取变量index = 1; 线程2:从主内存中读取变量index=1;
线程1:将index++,将修改的结果2write到主内存,同时线程2也做了同样的动作,将结果2write回主内存
实际期望值:3,两个线程操作完结果:2
以上便是线程不安全出现的问题。
那volatile线程安全吗,验证一下:
volatile int index = 0;
@Test
public void unSaveUpadate() throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1L);
index++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(1000L);
System.out.println("输出值:" + index);
}
结果显示也不安全,实际数据始终是小于1000.
那问题来了,volatile到底解决了什么问题,看看volatile的描述:
1:保证被修饰的变量对所有线程的可见性,值当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
2:禁止指令重排
以上描述可以看出volatile保证了被volatile修饰的变量保证可见性,但是没有描述保证一致性
volatile修饰的变量每次读之前会插入读内存屏障,读之前一定要从主内存拉取新的值,volatile修饰的变量写入工作内存之后,加写屏障,会将其他线程改变量的状态变成失效状态,其他线程读时一定要去读取最新的值
通过上述方法保证了可见性。
但是:
1:读取后
2:修改
3:写之前
在1和3之间,其他线程对变来你给做了修改任然会造成不一致的情况。
面试的时候有的面试官会问:为什么要做工作内存,都对主内存操作不就没有这个问题了吗?
计算机的内存可以包含主内存,和cup的高速缓存区,我们可以理解对cpu的高速缓存操作要远远快于对主缓存的操作,可以理解为ava为了提交运行速度,java虚拟机栈使用和操作的是cpu的高速缓存,
而主内存使用的是计算的主内存。