Volatile的理解
volatile是java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排
JMM:java内存模型,不存在,是一个约定。
JMM同步的约定
1.线程解锁前:必须把工作内存中的变量刷新回主内存
2.线程加锁前:必须把主内存中的变量加载到自己工作内存中
3.加锁和解锁是同一把锁
内存交互操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
以上是JMM主存与工作内存如何交互的,8中指令及规则。但是存在一个问题,如果线程A拿到主存中的flag,线程B也同样拿到,并且修改了falg的值。线程A不知道,出现了问题,代码演示如下
public class JMMDemo {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (flag){
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
flag = false;
System.out.println("结束");
}
}
设置一个flag = true,开启一个线程进入循环,主线程修改flag的值为false,开启的线程则应该停止但是运行结果打印结束程序未停止。说明线程A对主线程是不可见的。
那么我们如何保证它的可见性呢?volatile
private volatile static boolean flag = true;
这样主线程修改flag的值,线程A就会停止循环
我们继续看volatile的不保证原子性
public class VolatileTest {
private static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
new Thread(()->{
for (int j = 0; j < 100; j++) {
add();
}
}).start();
}
//主线程和GC垃圾回收线程为2,大于2线程礼让
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
结果达不到1000,我们可以在方法上加锁,能够达到1000,去掉锁,在num上加关键字volatile,发现它还是达不到1000.说明它是没有原子性的,我们除了加锁外还有没有其他方法保证它的原子性呢?
使用原子类,atomicInteger.getAndIncrement()加一的操作,底层为CAS
public class VolatileTest {
private static AtomicInteger atomicInteger = new AtomicInteger();
public static void add(){
atomicInteger.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
new Thread(()->{
for (int j = 0; j < 100; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(atomicInteger);
}
}
禁止指令重排,内存屏障,CPU指令保证其顺序
编译器优化重排
并行指令重排
内存系统重排