010-JMM内存模型与并发的可见性、有序性、原子性

并发

三大特性

  1. 原子性
  2. 可见性
  3. 有序性

原子性

对外部来说原子性内的操作要么全部成功,要么全部失败

在多线程场景下,如果共享变量为i
i++操作是不能保障原子性的
原因就是翻译成汇编语言的时候i++有4步

  1. 读取
  2. 入栈
  3. +1
  4. 设置
    一个线程A执行到“步骤3”后另一个线程B有可能也在执行3,4,此时在原来的线程A在步骤4设置其实就设置的不对

怎么保证

  • synchronized
  • Lock
  • CAS
  • 基础变量单个赋值操作使用volatile只能保证单个基本数据类型字段的赋值操作的原子性
    在32位机器上对64位长度的数据进行操作是不能保证原子性的
    所以需要加上volatile关键字

可见性

每个线程操作的都是线程的本地内存
如果本地没有读取到共享内存的数据就会出现可见性问题

比如:
存在一个共享变量state
一个线程A while(state){} 空转
另一个线程B 1s后修改state为false

线程A会一直空转下去
下边JMM模型详细分析

怎么保证

  • synchronized
  • Lock
  • volatile关键字
  • 内存屏障:java unsafe类提供的有

有序性

在不影响结果的情况下,会对代码指令进行重排
比如
int a = 3;
int b = 5;
int c = 7;
int d = a + b;

此时JVM就可能把int c = 7; 挪到最后执行
单线程下可能没有问题
但是多线程下可能存在问题
例如下边经典的例子:
在这里插入图片描述
例子中可能出现的结果如下三种
在这里插入图片描述
但是实际出现
在这里插入图片描述
这种结果就是发生了指令重排的结果
JVM无法保证多线程下的重排结果一定正确

怎么保证

  • synchronized
  • Lock
  • volatile关键字
  • 内存屏障

JMM内存模型

Java线程间的通讯由JMM(Java Memory Model)Java线程模型控制
JMM决定线程对共享变量的写入是否对另一个线程可见
在这里插入图片描述
JMM定义了线程、变量和内存之间的抽象关系
共享变量储存在主内存中。每个线程操作的都是从主内存读取的共享变量的副本,不能直接操作主线程

在这里插入图片描述

  1. 变量a read 到本地内存 load 作为本地内存中变量a副本
  2. use 读取工作内存的变量a的副本到线程中
  3. assign 赋值给本地内存中变量a的副本
  4. store 存储变量a的副本的值到主内存中 write 值写入变量a中

如果一个线程值进行1,2操作
那么就无法看到其他线程执行的3,4操作

为什么要有JMM

屏蔽不同操作系统内存使用的差异
主内存一般存储在内存中
本地内存一般是存储在CPU寄存器内高速缓存

但是这也是导致多线程环境下可见性和有序性问题的原因

解决方案

加锁

线程获取锁:本地内存失效
线程释放锁:所有本地内存共享变量直接刷新到主内存中

注意:加锁只能保证加锁线程的可见性和有序性
如果一个线程没有加锁,对它来说共享变量就算写回主内存还是不可见的

volatile

写一个volatile变量的时候,JMM会把变量直接刷新到主内存中
读一个volatile变量的时候,JMM直接从主内存读取
并且volatile禁止了某些场景的指令重排
假设有2个操作:
如果第二个是volatile写,不能重排
如果第一个是volatile读,不能重排
如果第一个是volatile写,第二个是volatile读,不能重排

volatile就是通过内存屏障来实现的

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值