写点什么

JVM 系列 -java 内存模型(JMM)

用户头像
诸葛小猿
关注
发布于: 2020 年 11 月 17 日
JVM系列-java内存模型(JMM)

Java内存模型(Java Memory Model ,JMM)与JVM运行时数据区是不一样的。这两者是完全不同的概念,绝对不能混为一谈。



一、JMM与JVM的区别



JVM运行时数据区,是Java虚拟机在运行时对该Java进程占用的内存进行的一种逻辑上的划分,包括方法区、堆内存、虚拟机栈、本地方法栈、程序计数器。这些区块实际都是Java进程在Java虚拟机的运作下通过不同数据结构来对申请到的内存进行不同使用。



Java内存模型,是Java语言在多线程并发情况下对于共享变量读写(实际是共享变量对应的内存操作)的规范,主要用于java程序访问共享内存时,屏蔽不同的操作系统、不同的硬件的差异,从而解决多线程可见性、原子性等问题



我们编写完程序后,编译器和处理器都会有相应的优化,以提高运行效率。优化分为很多种,比如指令重排序。指令重排序了,性能提高了,但是还能得到我们想要的执行结果吗?



优化的前提是执行的结果依然正确,这就需要有额外的保证,JMM就是给java程序员做保证的,保证优化后结果依然正确同时性能提高。



二、happens-before原则



JMM是如何保证提高性能的通过结果依然正确?JVM规范规定了Java虚拟机对多线程内存操作的一些规则:happens-before原则。主要体现在volatile,synchronized这两个关键字上。



happens-before(happens-before原则不能简单从字面理解成一个操作发生在另一个操作的前面)八大原则:



  • 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。

  • 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。

  • volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。

  • happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。

  • 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。

  • 线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。

  • 线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。

  • 对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。



在同一个线程中,书写在前面的操作happen-before后面的操作: 好多文章把这理解成书写在前面先发生于书写在后面的代码,但是指令重排序,确实可以让书写在后面的代码先于书写在前面的代码发生。这是里把happen-before 理解成“先于什么发生”,其实happen-beofre在这里没有任何时间上的含义。比如下面的代码:



int a = 3; //1
int b = a + 1; //2



这里 //2 对b赋值的操作会用到变量a,那么java的“单线程happen-before原则”就保证 //2的中的a的值一定是3,而不是0等其他值,因为//1 书写在//2前面, //1对变量a的赋值操作对//2一定可见。因为//2 中有用到//1中的变量a,再加上java内存模型提供了“单线程happen-before原则”,所以java虚拟机不许可操作系统对//1 //2 操作进行指令重排序,即不可能有//2 在//1之前发生。但是对于下面的代码:



int a = 3;
int b = 4;



两个语句直接没有依赖关系,所以指令重排序可能发生,即对b的赋值可能先于对a的赋值。



------



同一个锁的unlock操作happen-beofre此锁的lock操作: 话不多说直接看下面的代码:



public class A {
public int var;
private static A a = new A();
private A(){}
public static A getInstance(){
return a;
}
public synchronized void method1(){
var = 3;
}
public synchronized void method2(){
int b = var;
}
public void method3(){
synchronized(new A()){ //注意这里和method1 method2 用的可不是同一个锁哦
var = 4;
}
}
}



main(){
//线程1执行的代码:
A.getInstance().method1();
//线程2执行的代码:
A.getInstance().method2();
//线程3执行的代码:
A.getInstance().method3();
}



如果某个时刻执行完“线程1” 马上执行“线程2”,因为“线程1”执行A类的method1方法后肯定要释放锁,“线程2”在执行A类的method2方法前要先拿到锁,符合“锁的happen-before原则”,那么在“线程2”method2方法中的变量var一定是3,所以变量b的值也一定是3。但是如果是“线程1”、“线程3”、“线程2”这个顺序,那么最后“线程2”method2方法中的b值是3,还是4呢?其结果是可能是3,也可能是4。的确“线程3”在执行完method3方法后的确要unlock,然后“线程2”有个lock,但是这两个线程用的不是同一个锁,所以JMM这个两个操作之间不符合八大happen-before中的任何一条,所以JMM不能保证“线程3”对var变量的修改对“线程2”一定可见,虽然“线程3”先于“线程2”发生。



------



对一个volatile变量的写操作happen-before对此变量的任意操作:



volatile int a;
//线程1
a = 1; //1
//线程2
b = a; //2



如果线程1 执行//1,“线程2”执行了//2,并且“线程1”执行后,“线程2”再执行,那么符合“volatile的happen-before原则”所以“线程2”中的a值一定是1。



如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作:如果有如下代码块:



volatile int var;
int b;
int c;
//线程1
b = 4; //1
var = 3; //2
//线程2
c = var; //3
c = b; //4



假设“线程1”执行//1 //2这段代码,“线程2”执行//3 //4这段代码。如果某次的执行顺序如下:

//1 //2 //3 //4。那么有如下推导( hd(a,b)表示a happen-before b):



因为有hd(//1,//2) 、hd(//3,//4) (单线程的happen-before原则)

且hd(//2,//3) (volatile的happen-before原则)

所以有 hd(//1,//3),可导出hd(//1,//4) (happen-before原则的传递性)

所以变量c的值最后为4

如果某次的执行顺序如下:

//1 //3 //2// //4 那么最后4的结果就不能确定喽。其原因是 //3 //2 直接符合上述八大原则中的任何一个,不能通过传递性推测出来什么。



通过对上面的四个原则的详细解释,省下的四个原则就比较显而易见了。



三、总结



总结:happens-before原则主要体现在volatile,synchronized这两个关键字上。



  • volatile 是JVM提供的对共享变量在多线程读写时的可见性保证,主要作用是对volatile修饰的共享变量禁止被缓存(这里跟CPU的高速缓存和缓存一致性协议有关),不做重排序(重排序:在CPU处理速度远大于内存读写速度的现状下为了提高性能而进行的优化),但是并不保证共享变量操作的原子性。

  • synchronized 是JVM提供的锁机制,通过锁的特性和内存屏障保证锁住区域操作的原子性、可见性、有序性。

  • 锁争抢的是对象(static锁的是类对象,非static锁的是当前对象,即this,锁方法块锁的是自定义对象)在堆内存中对象头的一块内存的“主权”,只有一个线程能获取该“主权”,即排他性,通过锁的排他性保证对锁住区域的操作的原子性

  • 通过在代码前后加入加载屏障(Load Barrier)和存储屏障(Store Barrier),能保证锁住代码块或者方法中对共享变量的操作的可见性

  • 通过在代码前后加入获取屏障(Acquire Barrier)和释放屏障(Release Barrier),能保证锁住代码块或者方法中对共享变量的操作的有序性



参考:



https://www.cnblogs.com/tiancai/p/9636199.html



https://zhuanlan.zhihu.com/p/92341957



完成,收工!





传播知识,共享价值】,感谢小伙伴们的关注和支持,我是【诸葛小猿】,一个彷徨中奋斗的互联网民工。





发布于: 2020 年 11 月 17 日阅读数: 54
用户头像

诸葛小猿

关注

我是诸葛小猿,一个彷徨中奋斗的互联网民工 2020.07.08 加入

公众号:foolish_man_xl

评论

发布
暂无评论
JVM系列-java内存模型(JMM)