您的位置:首页 > 编程语言 > Java开发

Java 内存模型 与 高效并发

2016-09-02 00:00 267 查看
摘要: 并发处理的广泛应用使得Amdahl定律替代摩尔定律成为计算机性能发展源动力的根本原因,也是人类压榨计算机运算能力最有力的武器。

1. 并发解决了什么问题

多任务处理。

在处理器(CPU)运算速度 与 存储设备 (内存)、通讯设备 的速度差距极大的前提下,更有效的利用 处理器 的运算能力。

##2. 计算机内存模型

处理器

高速缓存

主内存



##3. JVM 内存模型 - JMM

java 线程

线程工作内存

主内存



##4. Java 中的 内存一致性交互操作

lock :
作用于主内存 变量,将变量标记为 线程 独占


unlock :
作用于主内存 变量, 释放线程独占状态


read :
作用于主内存 变量,将变量值从主内存传输到工作内存


load :
作用于工作内存 变量,将read 之后的变量值放到工作内存的变量副本


use :
作用于工作内存变量, 将 工作内存中的一个变量值传递给执行引擎(线程),当虚拟机遇到一个需要使用变量的字节码指令,就会执行这个操作


assign :
作用于工作内存的变量, 把 执行引擎的变量值赋值给工作内存的变量,当虚拟机遇到一个赋值变量的字节码指令,就会执行这个操作


store :
作用于工作内存变量,把工作内存中的一个变量值传输到主内存


write :
作用于 主内存变量,把store传输过来的变量回写到主内存




##4. Java 中的 内存一致性保证

[read,load] 、 [store,write] 必须成对出现,也就是不允许 从主内存中读取了数据工作内存不接受,或工作内存数据传输到主内存,主内存不回写。


不允许线程丢弃(回滚) assign 操作,也就是 变量在工作内存中改变之后,必须同步到主内存


不允许线程把数据 在没有 assign 之前就同步回主内存


变量只能在主内存中诞生,也就是不能将未进行 load ,assign 的变量进行 use 和 store ,也就是 load -> use; assign -> store 必须被保证


一个变量在同一时刻只接受一个线程的lock操作,注意可以接受同一线程的多次lock操作,只需要执行同样数目的 unlock操作就能释放锁


如果对变量执行了lock操作,将会清空工作内存中的这个变量,再次使用之前,必须重新执行 load 或者 assign 来初始或变量


如果一个变量没有被lock,则不允许对它执行unlock,也不允许一个线程 unlock 被其他线程lock 的变量


一个变量在unlock之前,必须把此变量同步回写到主内存,也就是 store-> write -> unlock


##
3ff0
4. JMM 对 volatile 变量的特殊规则

volatile 变量的特征

volatile 类型的变量具备 **可见性** ,也就是 一个线程修改了这个变量,新值对于其他线程是立即得知的(原理是volatile 变量每次被使用之前,必须要从主内存中同步刷新;对volatile变量的 修改 操作,也必须立即回写到主内存)


volatile 的变量禁止解释器进行指令重排优化


volatile 变量保证,即使对于 long和double 64位类型的数据,也不允许将其读写操作分为2次 32位的操作来进行(这里是JVM规范中的宽松要求,后面会细谈)


volatile 变量的可见性 == 同步?

volatile 可见性 != 同步


volatile 保证线程任意时刻拿到的数值是最新的,但是多个线程都要修改volatile变量的情况,从线程拿到变量数值开始,到对变量的重新赋值操作结束,这段时间是不会再从主内存同步的,这样其他线程如果修改了这个volatile变量,就会导致数据的不同步,其实主要原因就是,对变量的修改,赋值的操作并不是原子性的一步操作,而是需要多步操作,这样就导致了数据的异常


volatile 变量的使用场景

所有线程之中的运算结果,不依赖volatile变量的当前值,也就是说volatile 变量不参与到实际的运算;这里有个例外,如果只有一个线程可以修改volatile的数值,那么volatile也可以参与线程的结果运算,因为没有多个线程共同修改,就不会有数据异常


在条件判断中,变量不需要与其他的状态变量共同参与不变约束,也就是说 if(volatileValue && anotherBooleanValue) 这样的情况是不被允许的,因为条件判断的过程也不是原子性的,在条件判断的过程中,很可能volatile变量的数值已经被更改了,只有单独使用volatile变量作为条件判断的条件语句才可以被多线程使用。


指令重排优化

jvm 解释器 会对方法中的代码进行字节码级别的优化,来提高执行效率,可能会导致,代码实际执行的顺序,并不是源代码中的书写顺序;


指令重排优化,会保证,所有依赖某个变量赋值结果的地方,会得到正确的结果,也就是说: int a = intValueB; 这样的情况是会保证intValueB之前的操作会发生在对intValueB的使用之前,但是如果,一个变量在一段代码中没有参与到其他变量的赋值之中,也就是说这样的例子:


public class ResourceCheckHandler{

public boolean isCheck = false;

public void checkResource(){

// check ...

isCheck = true;

}

public static void main(String[] args){

final ResourceCheckHandler rcHandler
= new ResourceCheckHandler();
new Thread(new Runnable{
public void run(){
while(rcHandler.isCheck){
// turn to next handler ...
}
}}).start();
}}


注意这里,checkResource()方法中,isCheck 只是在方法结尾赋值为true,它的数值在这个方法中没有影响到其他变量的数值,那么这句代码的执行顺序,就有可能在指令重排优化的时候被提前到其他代码的前面,导致这个方法还没有执行结束,实际上isCheck变量就已经是 true 了。 解决的方法就是,把isCheck 定义为volatile,那么解释器在进行指令重排优化的时候就不会把isCheck进行重排了。


long/double 数据的JVM 宽松协定

JVM规范中,允许JVM的某个实现,选择对 64位数据类型的 load,store,read,write 不保证其原子性,可以分为2次对32位数据的读写过程,也就是 double 和 long的非原子协定(Nonatomaic Treatment of double and long Variables)


对于市面上的商用虚拟机,均选择把 64 位数据的读写一次原子操作实现,因而这种读写半个变量的情况基本不会发生,因此,对于 long、double的类型,不必每个变量都加上volatile的关键字修饰
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java JVM 并发 JMM 多线程