【Java并发编程与高并发解决方案】JAVA内存模型JMM以及乱序执行优化(重排序)

出现线程安全问题,一般是因为主存和工作内存数据不一致以及重排序,那今天就说一下这两个方面。

乱序执行优化

乱序执行优化是多核CPU为了提高效率而做的不符合代码规则的优化。

int a = 1;
int b = 1;
int c = a + b;
  • 正常我们看到的执行顺序是A-B-C,但是因为CPU的重排序,运行顺序有可能变为B-A-C,这时候结果是不会受到任何影响的。

JMM

  • 说到并发就要设计多个线程之间是如何通信的,通信可以分为两种:消息传递以及内存共享,而java主要使用到内存共享。说到内存共享就要先来看一下java的内存结构了。


    【Java并发编程与高并发解决方案】JAVA内存模型JMM以及乱序执行优化(重排序)_第1张图片
    image.png
  • Java的内存结构主要分为栈和堆。
    • 栈的速度仅次于寄存器,速度快,但是大小和生存周期是固定的
    • 主要用来存放一些基本类型的局部变量。
    • 不同的线程栈之间是相互独立的,存放的变量对于其他线程不可见,即使他们运行的是同一段代码。
    • 是运行的数据区,是动态分配的(比如new关键字),所以速度存取速度较慢
    • 存在垃圾回收,所以大小和生命周期是不确定的。
    • 是共享的,主要用来存放对象
【Java并发编程与高并发解决方案】JAVA内存模型JMM以及乱序执行优化(重排序)_第2张图片
enter image description here
  • 当一个线程栈持有一个对象,他只是持有了这个对象的引用(内存地址),真正的对象还是在堆上。
  • 一个对象的成员变量,无论是啥类型,都是跟着对象存在于堆上的。
  • 这时候就有个问题,如果多个线程,同时访问同一个对象的同一个方法,每个方法内又有局部变量。会发生啥呢。
    • 其实啥也不会发生,多个线程访问同一个方法,他们其实只是持有方法内局部变量的拷贝。

硬件结构和JMM的关系

【Java并发编程与高并发解决方案】JAVA内存模型JMM以及乱序执行优化(重排序)_第3张图片
enter image description here
  • 如果要从内存中读取数据处理后写回,都经历了什么(从硬件角度来看)
    • 首先cpu发出指令
    • cache会在合适的时机把数据从内存读过来
    • cpu寄存器去执行计算操作啥的
    • 寄存器把数据写回到cache
    • cache在合适的时机把数据(上一个博客中提到的缓存行)写回主存\
  • 硬件结构和JMM有啥关系呢
    • 咱们上面说的栈和堆是在主存里的~当然,也会在不同的时机存在于cpu的缓存和寄存器(寄存器的速度比缓存还要快)

硬件结构和JMM更抽象的关系

【Java并发编程与高并发解决方案】JAVA内存模型JMM以及乱序执行优化(重排序)_第4张图片
image.png
  • 前面说啦,java中线程的通信是通过内存的共享,那如果要完成一次通信,要经过这两步
    • 线程1从主存中把变量读到了本地内存,进行一系列操作后,写回到主存
    • 线程2把数据从主存中读到本地内存
  • 这里说的本地内存是一个抽象的概念,他包括了上面说的,cache/寄存器啥的。
  • 那线程安全为啥会产生呢,就是因为这个操作(基本上线程安全问题都是性能优化导致的)
    • 如果一个线程要使用一个共享变量,首先要读到本地内存,后续的读写操作都是作用于本地内存上的副本的,执行完一系列操作后某个时刻,这个共享变量才会被从本地内存写回到主存。
    • 注意,在写回到主存前,对于数据所执行的操作对于别的线程是不可见的。
    • 如果此时另一个线程读取了主存中的共享变量,就产生了“脏读“。可以使用关键字”volatile“来解决,这个后续会详细说。
  • 内存可见性JMM是通过happens-before关系来提供的。这个在参考资料有详细说,后面用到的时候也会写出来。

接下来干啥

  • 明天说一下同步操作和同步规则后,理论的知识就暂时到这里。接下来会说一下并发模拟用到的工具以及代码。

更详细的参考资料:

http://ifeve.com/java-memory-model-6/
https://juejin.im/post/5ae6d309518825673123fd0e

你可能感兴趣的:(【Java并发编程与高并发解决方案】JAVA内存模型JMM以及乱序执行优化(重排序))