[JUC-14] Java 内存模型

1、前置概念

  原子性(Atomicity):原子性是指一个操作是不可中断的,即使是在多个线程一起执行的情况下,一个操作一旦开始执行,就不会受到其他线程的干扰。
  比如,有两个线程同时对一个静态全局变量int num进行赋值,线程A给他赋值为1,线程B给他赋值为2,那么不管这两个线程以何种方式何种步调去执行,num的值最终要么是1要么是2,线程A和线程B在赋值操作期间,是不可能受到对方干扰的,这就是原子性的一个特点——不可被中断。

  可见性(Visibility):可见性是指在多线程情况下,当一个线程修改了某一个共享变量的值之后,其他线程是否能够立即知道这个修改。显然,对于串行线程来说,可见性问题是不存在的,因为你在任何一个操作步骤中修改了某个变量的值,那么在后续步骤中,读取这个变量的值,一定是修改后的新值。

  有序性(Ordering):有序性问题可能是比较难理解的一个问题。对于一个线程执行的代码而言,我们总是习惯地认为代码总是按照书写顺序从先往后依次执行,这在单线程环境下,确实如此,但是在多线程并发环境下估计就不见得了,程序的执行可能就会出现乱序,给人的感觉就是写在前面的代码可能在后面执行了。其实有序性问题的原因是因为程序在执行时,可能因为编译器优化的缘故,进行了指令重排的操作,重排后的指令与原指令的顺序未必一致。
  不过这里我们还需要强调一点,对于一个线程来说,它看到的指令执行顺序一定是一致的,也就是说指令重排是有个一基本前提的,就是必须保证串行语义的一致性,不管指令怎么重排序都不会使串行的语义逻辑发生问题。
  注意:指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。

2、JMM

2.1 概念

  JMM 的全称是 Java Memory Model(Java内存模型)。它是不存在的东西,是一种概念和约定。

  JMM的关键技术点都是围绕着多线程的原子性可见性有序性来建立的,这也是 Java 解决多线程并行机制的环境下,定义出的一种规则,意在保证多个线程间可以有效地、正确地协同工作。

  关于JMM的一些同步的约定:
  1、线程解锁前,必须把共享变量立刻刷回主存;
  2、线程加锁前,必须读取主存中的最新值到工作内存中;
  3、加锁和解锁是同一把锁。

2.2 内存模型

  JMM规定了内存主要划分为主内存工作内存两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。
在这里插入图片描述
  JVM在设计时候考虑到,如果JAVA线程每次读取和写入变量都直接操作主内存,对性能影响比较大,所以每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,直接在工作内存中操作,而不能直接去操作主内存中的变量。但是这样就会出现一个问题,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。因为JMM制定了一套标准来保证开发者在编写多线程程序的时候,能够控制什么时候内存会被同步给其他线程。

2.3 内存交互操作

  内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

  lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;

  unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

  read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用;(从堆读出数据(一份拷贝),但还没写入线程工作内存);

  load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;(将这份数据的拷贝写入了局部变量表中)

  use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;(将这份数据在需要执行的时候从局部变量表又传递到操作数栈中)

  assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;

  store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的 write 使用;

  write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

  8 种操作的示意图如下:
在这里插入图片描述
  JMM对这八种指令的使用,制定了如下规则:

  1、不允许 read 和 load、store 和 write 操作之一单独出现。即使用了 read 必须 load,使用了 store 必须 write;

  2、不允许线程丢弃他最近的 assign 操作,即工作变量的数据改变了之后,必须告知主存;

  3、不允许一个线程将没有 assign 的数据从工作内存同步回主内存;

  4、一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施 use、store 操作之前,必须经过 assign 和 load操作;

  5、一个变量同一时间只有一个线程能对其进行 lock。多次 lock 后,必须执行相同次数的 unlock 才能解锁;

  6、如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新 load 或 assign 操作初始化变量的值;

  7、如果一个变量没有被 lock,就不能对其进行 unlock 操作。也不能 unlock 一个被其他线程锁住的变量;

  8、对一个变量进行 unlock 操作之前,必须把此变量同步回主内存。

猜你喜欢

转载自blog.csdn.net/qq_29051413/article/details/108004580