JMM
java内存模板(概念):就是用来对java线程中内存变量读取改变的一些规范
JMM的一些约定
- 线程解锁前,必须吧共享变量刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一吧锁
- 不允许操作单独使用
JMM的执行流程
- 主存初始值变量
- 线程read(读取)变量到工作内存,load(加载)到线程工作内存
- 执行引擎将工作内存中的变量user(使用),使用完然后assign(赋值)给工作内存
- 工作内存将变量store(储存)主存中,然后write(写入)到主存中
volatile:java虚拟机提供的轻量级的同步机制
1.保证可见性
package com.wl.VolatileTest;
import java.util.concurrent.TimeUnit;
public class Demo1 {
//不加volatile程序就会死循环
//加了volatile保证可见性
private volatile static int num = 0;
public static void main(String[] args) {
new Thread(() -> {
while (num==1){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
2.不保证原子性
原子性:不可分割,线程A在执行任务的时候,不能被打扰,也不能被分割要么同时成功,要么同时失败
Thread.yield();:让出计算机资源并重新竞争资源
public class Demo2 {
//volatile 不保证原子性
private volatile static int num = 0;
private static void add() {
num++;
}
//理论上num应该为20000
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}//java 默认执行两个线程 main gc
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"=>"+num);
}
}
如果不加lock和synchronized,怎么保证原子性:使用juc的原子类(atom) :
public class Demo2 {
//volatile 不保证原子性
private volatile static AtomicInteger num = new AtomicInteger();//juc的原子类(atom)
private static void add() {
num.getAndIncrement();//(低层使用cas:cpu的并发 效率高)在内存中修改值
}
//理论上num应该为20000
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}//java 默认执行两个线程 main gc
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"=>"+num);
}
}
原子类底层和操作系统挂钩!在内存中修改值!Unsafe类是一个特殊的存在
3.禁止指令重排
指令重排:你写的程序,计算机并不是按照你写的执行的(他会根据系统要求将执行顺序交换)
源代码–》编译器优化重排–》指令并行也可能重排–》内存系统也会重排–》执行
处理器指令重排会考虑系统间的依赖问题
volatile可以避免指令重排:内存屏障。cpu指令
1.保证特定的操作执行顺序
2.可以保证某些变量的内存可见性
理解:当使用volatile时,他会在前后加内存屏障(内存屏障:禁止上下指令顺序交换,单例使用较多)