Java内存模型(JMM)

11 篇文章 0 订阅

是什么

JMM(Java Memory Model)是一组规范,需要各个JVM的实现来遵守JMM规范,以便于开发者可以利用这些规范,更方便地开发多线程程序。

如果没有这样的一个JMM内存模型来规范,那么很可能经过了不同JVM的不同规则的重排序之后,导致不同的虚拟机上运行的结果不一样,那是很大的问题。

如果没有JMM,那就需要我们自己指定什么时候用内存栅栏等,JMM让我们只需要用同步工具和关键字就可以开发并发程序。

 最重要的3点内容

重排序:

好处:提高处理速度

重排序3种情况:

1,编译器优化:包括JVM,JIT编译器等

2,CPU指令重排:就算编译器不发生重排序,CPU也可能对指令进行重排

3,内存的“重排序”:线程A的修改线程B却看不到,引出可见性问题

可见性: 

为什么会有可见性问题:

CPU有多级缓存,导致读的数据过期

高速缓存的容量比主内存小,但是速度仅次于寄存器,所以在CPU和主内存之间就多了Cache层

线程间对于共享变量的可见性问题不是直接由多核引起的,而是由多缓存引起的

如果所有核心都只用一个缓存,那么也就不存在可见性问题

每个核心都会将自己需要的数据读到独占内存种,数据修改后也是写入到缓存中,然后等待刷入到主存中,所以会导致有些核心读取的值是一个过期的值

主内存和抽象内存:

Java作为高级语言,屏蔽了底层细节,用JMM定义了一套读写内存数据的规范,抽象为主内存和本地内存

主内存和抽线内存的关系:

1,所有的变量都存储在主内存中,同时每个线程也有自己独立的工作内存,工作内存中的变量内容是主内存中的拷贝

2,线程不能直接读写主内存中的变量,而是只能操作自己的工作内存中的变量,然后再同步到主内存中

3,主内存是多个线程共享的,但线程间不共享工作内存,如果线程间需要通信,必须借助主内存中转来完成

happens-before:

happens-before规则是用来解决可见性问题的:在时间上,动作A发生在动作B之前,B保证能看见A,这就是happens-before

两个操作可以用happens-before来确定它们的执行顺序:如果一个操作happens-before于另一个操作,那么我们说第一个操作对于第二个操作是可见的

能保证可见性的措施:

volatile、synchronized、Lock、并发集合、Thread.join()、Thread.start()等

volatile:

是什么:

volatile是一种同步机制,比synchronized或者Lock相关类更轻量,因为使用volatile并不会发生上下文切换等开销很大的行为

如果一个变量被修饰成volatile,那么JVM就知道了这个变量可能会被并发修改

但是开销小,相应的能力也小,volatile做不到synchronized那样的原子保护,volatile仅在很有限的场景下才能发挥作用

适用场合:

不适用:a++;

适用场合1:boolean flag,如果一个共享变量自始至终只被各个线程赋值,而没有其他操作,那么就可以用volatile来代替synchronized或者代替原子变量,因为赋值自身是由原子性的,而volatile又保证了可见性,所以就足以保证线程安全

适用场景2:作为刷新之前变量的触发器

作用:

1,可见性:读一个volatile变量之前,需要先使用相应的本地缓存失效,这样就必须到主内存读取最新值,写一个volatile属性会立即刷入到主内存

2,禁止指令重排序优化:解决单例双重锁乱序问题

volatile和synchronized的关系:

volatile可以看做是轻量版的synchronized:如果一个共享变量自始至终只被各个线程赋值,而没有其他操作,那么就可以用volatile来代替synchronized或者代替原子变量,因为赋值自身是由原子性的,而volatile又保证了可见性,所以就足以保证线程安全

总结:

1,volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如boolean flag;或者作为触发器,实现轻量级同步

2,volatile属性的读写操作都是无锁的,它不能替代synchronized,因为他没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁上,所以是低成本的

3,volatile只能作用于属性,我们用volatile修饰属性,这样compilers就不会对这个属性做指令重排序

4,volatile提供了可见性,任何一个线程对其的修改将立马对其他线程可见。volatile属性不会被线程缓存,始终从主存中读取

5,volatile提供了happens-before保证,对volatile变量v的写入,happens-before所有其他线程后续对v的读操作

6,volatile可以使得long和double的赋值是原子的

synchronized可见性:

synchronized不仅保证了原子性,还保证了可见性

进入synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。

原子性:

定义:一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分割的

Java中的原子操作:

除long和double之外的基本类型(int,byte,boolean,short,char,float)的赋值操作

所有引用reference的赋值操作,不管是32位机器还是64位机器

java.util.concurrent.atomic.*包中所有类的原子操作

long和double的原子性:

对于64位的值的写入,可以分为两个32位的操作进行写入,不具备原子性,可以使用volatile关键字解决。在32位JVM上,long和double的操作不是原子的,但是在64位的JVM上是原子的。实际开发中,商用Java虚拟机不会出现此问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值