和JVM内存模型的区别

我们常说的JVM内存模型:堆、线程栈、本地方法栈、元空间、PC寄存器,是指JVM运行过程中的物理内存划分。
而为了保证跨平台的一致性,JMM更多的是逻辑概念的上内存划分,是对计算机内存和高速缓存模型的一种借鉴实现。也就相应的有主内存和工作内容。
image.png

基本原理

JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。

三个特性

  1. 原子性:一个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态。
  2. 可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量的这种修改(变化)。
  3. 有序性:对于一个线程的代码而言,我们总是以为代码的执行是从前往后的,依次执行的。这么说不能说完全不对,在单线程程序里,确实会这样执行;但是在多线程并发时,程序的执行就有可能出现乱序。用一句话可以总结为:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。

    关于synchronized和volatile

  4. synchronized/ReentrantLock

    1. 进入同步块时,会将使用到的变量从主存中重新加载。
    2. 退出同步块时,会将变更后的变量写入到主存中。
    3. 保证了原子性和可见性,但没有保证有序性。
  5. volatile

    1. 通过内存屏障进行数据变更实时刷新到主存。
    2. 保证了可见性,不能保证原子性和有序性。

      指令重排序和内存屏障

  6. 指令重排序

    1. 核心设计原因在于提高cpu性能。
    2. 不改变单线程程序语义的前提下,也就是指令之间没有因果关系,但这种因果关系是从单线程内去分析的,在多线程的视角下因果关系就可能存在冲突。