博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 内存模型 与 高效并发
阅读量:5968 次
发布时间:2019-06-19

本文共 2995 字,大约阅读时间需要 9 分钟。

hot3.png

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

##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的关键字修饰

转载于:https://my.oschina.net/samuelzuuka/blog/741461

你可能感兴趣的文章
定时压缩log日志文件
查看>>
秋无痕 Windows XPSP3 集成安装增强版 V201306
查看>>
IT男成都租房记
查看>>
博为峰JavaEE技术文章 ——MyBatis Provider之@SelectProvider SQL方法
查看>>
Java核心API -- 9(异常)
查看>>
apache 编译报错:undefined reference to `apr_array_clear'
查看>>
图像识别DM8127开发攻略——UBOOT的移植说明
查看>>
ubuntu 下升级docker版本
查看>>
EXSi5.5安装篇
查看>>
开始记录吧
查看>>
windows下用php开发类似百度文库应用需要的工具和问题
查看>>
css模拟select设置高度在ie67下有效(也可作为去除边框)
查看>>
互联网思维
查看>>
ecshop备份数据 ecshop转移数据 ecshop更换主机
查看>>
手机将与瘦客户机争夺办公桌面
查看>>
ubuntu下针对php的thrift 安装折腾记录
查看>>
使用C#客户端访问FTP服务的一个解决方案
查看>>
对软件测试团队“核心价值”的思考
查看>>
mysql基础知识点
查看>>
Microsoft.System.Center.Operations.Manager.2007 中文版完整光盘下载地址
查看>>