JMM内存模型
JMM内存模型是java内存模型,又叫java线程内存模型。
首先理解一下并发和并行的概念。
并发:是一种逻辑架构,指的是在同一时刻只能由一条指令执行,在宏观意义上看起来是多个进程或者线程同时执行,但是在微观意义上,只是把时间分成若干段,多个进程之间快速交替的执行。可以在单核或者多核上执行。
并行:在同一时刻,有多条指令在多个处理器上同时执行,无论是在宏观或者微观来看,二者都是在一块执行的。只会在多核处理器上发生。
JMM是一种逻辑架构,每个线程都有自己的本地方法栈,本地方法栈中局部变量表存放的是本地变量 和 共享变量的副本。线程要读取共享变量的数据,首先要从主内存中拷贝一份到本地内存,然后再去使用。
下面通过一个案例来分析JMM内存工作模式
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
//线程A
new Thread(()->{
System.out.println("A线程执行");
int i = 0;
while (flag){
i++;
}
System.out.println("A线程执行完毕,i的值为"+i);
}).start();
Thread.sleep(100);
//线程B
new Thread(()->{
flag = false;
System.out.println("B线程修改flag值为false");
}).start();
}
线程A读取共享变量flag循环增加i的值,当跳出循环时,打印i的值。线程B等待100毫秒后启动,改变flag为flase。正常情况下,当flag是false时,线程A中的循环语句就会跳出,但是在控制台我们发现程序一直在运行,线程A并没有停止,那就说明线程A读取的flag是true
第一步首先在主内存中拷贝flag=true到本地内存A,执行i++操作,然后线程B在主内存中拷贝flag=true到本地内存B,线程B修改flag=flase,立刻会刷新到本地内存,但是要过一段时间才会刷新到主内存,而此时线程A的本地内存的共享变量为失效时,才会读取主内存的变量。所以程序这样的情况时因为线程A的本地内存一直在使用副本变量,并没有去主存中做更新。
volatile
volatile关键字保证了变量的可见性和有序性,但不能保证原子性。
可见性
先来解释一下volatile为什么会保证可见性(可见性就是指当一个线程修改了变量之后,另一个线程会立马看到修改的值),还是使用上一个例子,这次把flag变量加是volatile,再来看一下运行结果,可以看到结果是线程A跳出了循环,打印i的值。
使用的是汇编指令lock,通过缓存一致性协议(MESI),锁缓存行来实现可见性。
为什么不能保证原子性,所谓的原子性指的是一组指令要么全部成功,要么全部失败。拿i++举例,该操作分为三步:
1,线程读取i的值
2,i进行自增计算
3,刷新回i的值。
volatile int i = 1,两个线程同时执行i++操作,即使加了volatile关键字,当A和B线程同时执行完1,2步骤时,此时在内存中的i还没有发生变化,假设A线程先执行了第三步,此时触发缓存一致性协议,线程B立即修改i的值为2,但是线程B已经执行过了i++操作,只不过还没有执行第三步,现在再执行第三步,i依旧等于2。所以volatile 并不能保证原子性。
有序性
什么是有序性,我们的程序中写的代码CPU中可能是乱序执行的,就是所谓的指令重排,这样会提交执行的效率。但是有一个规范,就是在单线程的情况下是不能影响最终执行的结果(as-if-serial语义)。但是在多线程的情况下可能会出现问题,有序性就是确保程序指令顺序执行。
volatile禁止指令重排,通过内存屏障来完成。内存屏障类似AOP的概念,在指令前后加入一些指令禁止重排序。
happens-before
因为指令重排会影响到多线程之间的安全问题,需要happens-before原则来规定一些禁止编译优化的场景,保证并发编程的安全。
happens-before8大规则
1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
2.锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;
3.volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
4.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
5.线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
;
7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
8.对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;