一、什么是JMM?
即Java内存模型
主存:所有线程都共享的数据(静态变量、成员变量)
工作内存:每个线程私有的(对应局部变量)
可见性:由JVM缓存优化引起
有序性:由JVM指令重排序优化引起
二、JMM体现一下几个方面
1、可见性
保证指令不会受到CPU缓存的影响
缘由:t1、t2线程,t2线程从主存中读取数据(JIT编译器会把值缓存在线程己工作内存中,减少对主存中的访问,以提高效率),但是此时t1线程已经修改了主存中的值了,但是t2线程不会从主存中读取数据,只会在自己工作内存读取缓存的数据,每次读取的都是旧值,所以造成真实数据的不可见。
如何解决?
给变量加上volatile(修饰成员变量和静态成员变量)或者加锁,其作用是不会在缓存中读取数据了,只能在主存中读取数据,解决可见性问题。
2、原子性
保证指令不会受到上下文切换的影响(临界区代码的保护)
3、有序性
保证指令不会受到CPU指令并行优化的影响。
为什么重排序?
CPU希望对指令并行处理,调整了指令的顺序
JVM调整指令的顺序会发生指令重排,对代码进行优化以提高效率,在单线程下发生指令重排不会出现问题,在多线程下却是会影响结果的正确性。
指令重排序(为了实现指令集的并行效果)
为了让指令并行的执行,可能会调整指令的次序以达到高并发,提高处理的速度。重排序的前提是排序不能影响结果,但是多线程无法避免,可能会影响结果
三、Volatile与JMM
1、作用
Volatile可以禁止重排序,所以volatile既可以保证有序性,又可以保证可见性。
2、Volatile原理
Vola的底层实现的是内存屏障
会在volatile变量的写指令后加入一个写屏障
会在volatile变量的读指令前会加入一个读屏障
Volatile如何保证可见性?
写屏障保证该屏障之前的,对共享变量的改动,都同步到主存当中。下列代码ready加入了volatile关键字
读屏障保证该屏障后,对共享变量的读取,都加载的是主存中最新的数据
Volatile如何保证有序性?
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后。
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前。
四、总结
读屏障能保证之后的读能读到最新的结果,但不能保证读跑到前面去,如下图,t1线程先读了,任会发生指令交错。有序性也是保证自己的线程内的相关代码不被重排序。
所以Volatile能保证有序性和可见性,不能处理指令交错的问题,即无法实现原子性,而synochronized可以保证有序性、可见性、原子性(前提是保证共享变量都交给synochronized管理,才能保证原子性、有序性、可见性,因为synochronized里面的代码仍会发生重排序,synochronized会保证其代码里面的不被影响,但是有一部分在外面的话加上在synochronized里面可能会发生重排序就会造成问题)。