JMM内存模型
java内存模型(Java Memory Module)
虚拟机并不是直接操作主内存,而是又创建了一个缓存,类似cpu的高速缓存,要操作内存中的数据,一般会进行多步原子操作,这就会导致多个线程访问会出现一些问题。
JMM的原子操作如下图说明
volatile关键字
/**
* 循环里有代码,则会跳出循环,不知道什么原因
*/
public class VolatileTest {
private static boolean flag = false; // 不适用volatile修饰,可能会进入死循环
public static void main(String[] args){
new Thread(()->{
while (!flag){
///System.out.println("flag已修改"); 循环里有代码,则会跳出循环,不知道什么原因
}
System.out.println("线程1停止");
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("修改flag的值");
flag = true;
}).start();
}
}
并发编程三大特性:可见性,有序性,原子性。volatile能保证可见性和有序性,有两个主要作用,可见和禁止代码重排
可见性:一个线程修改了某个共享变量的值,另一个线程可以感知到
指令重排:在不影响单线程执行结果的情况下,计算机为了最大限度的利用cpu的性能,会对机器指令重排优化
重排的原则:as-if-serial和happen-before
volatile的实现原理:利用CPU的缓存一致性协议MESI,volatile马上将改变的变量写入主内存并开始MESI协议
双重检测锁实现的单例,对象半初始化:由于指令重排的关系,可能会先执行对象闯将的init()方法,然后再执行putstatic,单线程没问题,但多线程会有问题
class DoubleCheckInstance{
private static DoubleCheckInstance instance = null;
// 建议添加volatile关键字,避免代码重排
// private static volatile DoubleCheckInstance instance = null;
private DoubleCheckInstance(){}
public static DoubleCheckInstance getInstance() {
if(instance == null){// 如果a, b线程同时执行到这一步,符合判断,进入同步代码块
synchronized (DoubleCheckInstance.class){
if(instance == null){ // 如果这里没有判断,则总是会执行new对象代码
instance = new DoubleCheckInstance(); // 创建对象有三个指令1. 分配对象的内存空间 2. 初始化对象 3. 设置instance指向刚分配的内存地址
// 可能会出现指令重排的情况,导致先执行3,在执行2,如果是单线程没有问题,多线程就会有问题
}
}
}
return instance;
}
}
https://blog.csdn.net/qq_37960603/article/details/103944203
https://blog.csdn.net/DJYDFT2831djydft/article/details/113773631
volatile关键字说明
volatile保证有序性:使用内存屏障,JVM规范定义的内存屏障,可以保证指令的顺序