JUC多并发编程-->JMM


JMM java 内存模型:
MM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存它仅仅描述的是 一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关建技术点都是围绕多线程的 原子性、可见性和有序性展开的。

  • 通过JMM来实现线程和主内存之间的抽象关系
  • 屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序在各种平台下都能达到一致的内存访问效果。
    在这里插入图片描述

三大特性

原子性、可见性和有序性

  • 可见性: 当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更,JMM规定了所有的变量都存储在主内存中

系统主内存共享变量数据修改被写入的时机是不确定的,多线程并发下很可能出现“脏读”,所以每个线程都有自己的工作内存,线程自己的工作内存#保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等 ) 都必需在线程自己的工作内存中进行,而不能够直接读与主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成
在这里插入图片描述

  • 原子性:一个操作是不可被打断的,在多线程环境下,操作不能被其它线程干扰
  • 有序性:对于一个线程的执行代码而言,我们总是习惯性认为代码的执行总是从上到下,有序执行。但为了提升性能。编译器和处理器通常会对指今序列进行重新排序。Java规范规定JVM线程内部维持顺序化语义,即只要程序的最终结果与它顺序化执行的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。
    • JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指今进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能。但是,指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致(即可能产生”脏读"),简单说,两行以上不相干的代码在执行的时候有可能先执行的不是第一条,不见得是从上到下顺序执行,执行顺序会被优化。
    • 执行的过程
      在这里插入图片描述

多线程对变量的读写过程
在这里插入图片描述

happens-before

多线程先行发生原则–happens-before

  • 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
  • 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

八条规则

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;也就是前一个操作的结果可以被后续的操作获取。
  2. 管程锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;(此处后面指时间的先后)
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;(此处后面指时间的先后)
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则操作A先行发生于操作C。
  5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生(Thread.interrupted() );
  7. 线程终止规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
  8. 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于他的finalize()(通常的目的是在对象不可撤销地丢弃之前执行清理操作)方法的开始;

volatile

  • 被volatile修饰的变量有可见性排序性(排序有时候需要禁重排)俩个特点

volatile内存语义

  • 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
  • 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量
  • 所以volatile的 写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。

内存屏障Memory Barrier

是一种屏障指令,使得CPU或编译器对屏障指令的前和后所发出的内存操作 执行一个排序的约束,也叫内存栅栏或栅栏指令,阻止屏障俩边的指令重排
volatile依靠内存屏障Memory Barrier保证可见性和有序性
禁重排:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序不存在数据依赖关系,可以重排序; 存在数据依赖关系,禁止重排序 但重排后的指令绝对不能改变原有的串行语义!

内存屏障

  • 内存屏障(也称内存栅栏,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。

  • 内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性(禁重排),但volatile无法保证原子性。

  • 内存屏障之前所有写操作都要回写到主内存中

  • 内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现可见性)

  • 写屏障(Store Memory Barrier): 告诉处理器在写屏障之前将所有存储在缓存(store bufferes)中的数据同步到主内存。也就是说当看到Store屏障指令,就必须把该指令之前所有写入指令执行完毕才能继续往下执行。
    在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中。

  • 读屏障(Load Memory Barrier): 处理器在读屏障之后的读操作,都在读屏障之后执行。也就是说在Load屏障指令之后就能够保证后面的读取数据指令一定能够读取到最新的数据。
    在读指令之前插入读屏障,让工作内存或CPU高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据。

因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。

四种内存屏障

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
重排规则:
在这里插入图片描述

  • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障,禁止处理器把上面的volatile读与下面的普通读重排序
  • 在每个 volatile 读操作的后面插入一个 LoadStore 屏障,禁止处理器把上面的volatile读与下面的普通写重排序
    在这里插入图片描述
  • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障,可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中。
  • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障,作用是避免volatile写与后面可能有的volatile读/写操作重排序

在这里插入图片描述

volatile变量的读写过程

Java内存模型中定义的8种每个线程自己的工作内存主物理内存之间的原子操作
read(读取)-load(加载)-use(使用)-assign(赋值)-store(存储)-write(写入)-lock(锁定)-unlock(解锁)

在这里插入图片描述
在这里插入图片描述

原子性

volatile没有原子性
不适合参与到依赖当前值的运算(比如i++等)
可以用于保存某个状态的boolean值或者int值
volatile只能保证可见性,因此在不符合以下俩条规则的运算场景中,仍要通过加锁保证原子性:

  • 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束

举例 :i++
丢失写操作
在这里插入图片描述

适用情况

  • 单一赋值可以,but含复合运算赋值不可以(i++之类)
  • 状态标志,判断业务是否结束(flag=true)
  • 开销较低的读,写锁策略(写的时候加锁,读的时候可以设置volatile)
  • DCL双端锁的发布
    在这里插入图片描述

字节码层面

代码中加入volatile后,字节码层面加入了一个ACC_VOLATILE,JVM在把字节码生成机器码的时候,发现操作是volatile变量的话,会按照JMM的规范,在相应的位置插入内存屏障

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值