一点一点

JMM

JMM

前言

现代计算机多核并发缓存架构

由于CPU执行速度较快,主内存执行速度较慢,CPU在大多数情况下是不与主内存进行直接操作的 一个程序的启动首先从硬盘将数据加载到主内存,主内存再次加载到CPU的各级缓存里 之后程序的运行操作数据都是在CPU的高级缓存里的 ,如果数据发生更新,会同步到主内存,最终保存到硬盘。

JAVA内存模型

简图

Java内存模型与CPU的内存模型相似 每个线程都拥有一个独立的工作内存,Java内存模型规定所有的变量都存储在主内存中,而每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量

关键概念

原子性

原子性指一个操作是不可中断的。即使多个线程一起执行,一旦一个操作开始执行,就不会被其他线程干扰

JMM中的8种原子性操作

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
  • store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
  • unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;

可见性

可见性指一个线程修改了某个共享变量的值,其他线程能立刻得到通知

有序性

有序性指程序执行的顺序按照代码的先后顺序执行。

在JMM中,提供了以happens-before原则方式来保证有序性:

  • happens-before原则

    happens-before原则是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到。“影响”包括修改了内存中共享变量的值、 发送了消息、 调用了方法等。

  1. 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。 准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、 循环等结构。
  2. 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。 这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
  3. volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后顺序。
  4. 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
  5. 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、 Thread.isAlive()的返回值等手段检测到线程已经终止执行。
  6. 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
  7. 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
  8. 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

JMM原子性操作详解

线程使用主内存共享变量执行过程

当主内存有一共享变量flag 线程1 使用时执行过程

首先从主内存执行read操作读出共享变量,线程1工作执行load操作将共享变量加载到自己的工作内存中,工作内存执行use操作将共享变量给相关的执行引擎

当线程2修改主内存共享变量时执行过程

从主内存读取过程与上面相同 相关执行引擎修改值之后工作内存执行assign操作将工作内存里的共享变量修改为最新的值,工作内存执行store操作将值发送到主内存,主内存执行write操作将值写入主内存

存在问题

当线程2修改flag的值后 线程与线程之间不能沟通 线程1执行过程中使用的仍为自己工作内存的数据 出现了数据不一致 存在可视性问题

MESICPU缓存一致性协议中有一个CPU总线嗅探机制

CPU与内存之间的数据交互都通过总线进行,CPU总线嗅探机制(监听)保证了数据的一致性 ,当执行修改操作时CPU总线会通知所有内存中的此变量全部失效,当此线程再次使用此共享变量时只能从新从主内存获取。

在java多线程中开启MESI缓存一致性协议需要为共享变量 添加关键字volatile

volatile底层通过汇编lock前缀指令会锁定当前内存区域的缓存并立即将缓存写回到主内存即执行store 和write操作执行时,对当前主内存中的此此变量区域加锁lock,当写入完成释放锁unlock 由于此过程执行速度非常快因此不会对线程造成什么影响
volatile可以保证可视性和有序性但不能保证原子性,要保证原子性需要借助synchronized这样的锁机制

posted @ 2020-07-01 14:22  6风筝9  阅读(173)  评论(0编辑  收藏  举报