标签:虚拟机 对象 无法 lis 机器 语言 执行 学习 monitor
本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。
之前的文章中讲到,JMM是内存模型规范在Java语言中的体现。JMM保证了在多核CPU多线程编程环境下,对共享变量读写的原子性、可见性和有序性。
本文就具体来讲讲JMM是如何保证共享变量访问的有序性的。
在说有序性之前,我们必须先来聊下指令重排,因为如果没有指令重拍的话,也就不存在有序性问题了。
指令重排是指编译器和处理器在不影响代码单线程执行结果的前提下,对源代码的指令进行重新排序执行。这种重排序执行是一种优化手段,目的是为了处理器内部的运算单元能尽量被充分利用,提升程序的整体运行效率。
重排序分为以下几种:
通过指令重排的定义可以看出:指令重拍只能保证单线程执行下的正确性,在多线程环境下,指令重排会带来一定的问题(一个硬币具有两面性,指令重排带来性能提升的同时也增加了编程的复杂性)。下面我们就来展示一个列子,看看指令重排是怎么影响程序执行结果的。
public class Demo {
int value = 1;
private boolean started = false;
public void startSystem(){
System.out.println(Thread.currentThread().getName()+" begin to start system, time:"+System.currentTimeMillis());
value = 2;
started = true;
System.out.println(Thread.currentThread().getName()+" success to start system, time:"+System.currentTimeMillis());
}
public void checkStartes(){
if (started){
//关注点
int var = value+1;
System.out.println("system is running, time:"+System.currentTimeMillis());
}else {
System.out.println("system is not running, time:"+System.currentTimeMillis());
}
}
}
对于上面的代码,假如我们开启一个线程调用startSystem
,再开启一个线程不断调用checkStartes
方法,我们并不能保证代码执行到“关注点”处,var变量的值一定是3。因为在startSystem方法中的两个赋值语句并不存在依赖关系,所以在编译器进行代码编译时可能进行指令重排。所以真实的执行顺序可能是下面这样的。
started = true;
value = 2;
也就是先执行started = true;
执行完这个语句后,线程立马执行checkStartes方法,此时value值还是1,那么最后在关注点处的var值就是2,而不是我们想象中的3。
有序性定义:即程序执行的顺序按照代码的先后顺序执行。
在JMM中,提供了以下三种方式来保证有序性:
happens-before原则是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到。“影响”包括修改了内存中共享变量的值、 发送了消息、 调用了方法等。
下面是Java内存模型下一些“天然的”先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。 如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们随意地进行重排序:
这边举个列子来帮助理解happens-before原则:
private int value=0;
pubilc void setValue(int value){
this.value=value;
}
public int getValue(){
return value;
}
假设两个线程A和B,线程A先(在时间上先)调用了这个对象的setValue(1),接着线程B调用getValue方法,那么B的返回值是多少?
对照着hp原则,上面的操作不满下面的任何条件:
所以一条规则都不满足,尽管线程A在时间上与线程B具有先后顺序,但是,却并不满足hp原则,也就是有序性并不会保障,所以线程B的数据获取是不安全的!!
时间先后顺序与先行发生原则之间基本没有太大的关系,所以我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准。只有真正满足了happens-before原则,才能保障安全。
如果不能满足happens-before原则,就需要使用下面的synchronized机制和volatile机制机制来保证有序性。
volatile的底层是使用内存屏障来保证有序性的。
内存屏障有两个能力:
特性 | volatile关键字 | synchronized关键字 | Lock接口 | Atomic变量 |
---|---|---|---|---|
原子性 | 无法保障 | 可以保障 | 可以保障 | 可以保障 |
可见性 | 可以保障 | 可以保障 | 可以保障 | 可以保障 |
有序性 | 一定程度保障 | 可以保障 | 可以保障 | 无法保障 |
标签:虚拟机 对象 无法 lis 机器 语言 执行 学习 monitor
原文地址:https://www.cnblogs.com/54chensongxia/p/12120117.html