【Java 快速复习】 Java 内存模型 & 并发问题本质

【Java 快速复习】 Java 内存模型 & 并发问题本质

在 Java 领域,我们经常会说两个名词大家要有所区分:

  1. JVM 内存模型:这个所说的是 JVM 内存的划分规则,如 堆、栈、元空间等
  2. Java 内存模型:这个所说的是线程和主内存之间,以及 CPU 高速缓存和内存之间的抽象关系,,这种抽象关系带来的就是 Java 并发问题,Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。具体来说,这些方法包括 volatile、synchronized 和 final 三个关键字
并发问题源头

主要是三个问题:

  1. 缓存带来的可见性问题。
  2. 编译优化带来的有序性问题。
  3. 线程切换带来的原子性问题 (Java 语言分解成 CPU 指令操作并不是原子)。

可见性

多核 CPU 缓存(寄存器) 和 内存间存在可见性问题

A 线程 使用 CPU-1 读取内存中变量 X = 1 ;

B 线程 使用 CPU-2 读取内存中变量 X = 1;

A B 线程操作变量都是在本核 CPU 缓存中执行操作,最后写回。这个操作过程对于 A B 两线程是不可见的。

在这里插入图片描述

这就是可见性问题,解决该问题即,禁用 CPU 缓存,Java 中 volatile 关键字

有序性

Java 程序在编译过程中为了优化性能,有时会改变程序中语句的先后顺序

int a = 1;
int b = 2;

优化后可能为

int b = 2;
int a = 1;

这类优化调整了语句的顺序但不影响程序的最终结果。

其实我们在聊有序性的时候,最有可能遇到的问题是单例初始化时的双重检查。下面是一个比较常见的双重检查获取单例对象的代码

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

这个 getInstance() 方法看上去没有什么问题,但在指令重排后就有可能引发并发问题。

我们认为的 instance 赋值过程是

  1. 划分一块内存 M
  2. 在 M 中初始化 Singleton 对象
  3. 将 M 内存地址赋值给 instance 变量

而实际指令重排后

  1. 划分一块内存 M
  2. 将 M 内存地址赋值给 instance 变量
  3. 在 M 中初始化 Singleton 对象

这会导致在第二步的时候 instance 变量就不为 null 了,但此时对象还未初始化,如果多线程调用就可能返回一个未初始化的对象,从而导致程序错误。

同样的解决该问题的方法就是禁用指令重排,在 Java 中使用 volatile 关键字

原子性

CPU 能够保证的原子操作是 CPU 指令级别的,而一个 Java 语句可能会对应多个 CPU 指令,这就导致 Java 的单个语句可能并不是原子的,在线程切换的时候就可能出现并发问题,所以需要我们在 Java 语言的级别保证某些操作的原子性。

比如执行一个 +1 的操作

int count = 0;
void addOne(){
	count++;
}

如果有多个线程同时调用 addOne() 方法,这个最终的总数是不准确的。

对于 ++ 操作拆解成 CPU 指令至少需要三条:

  1. 将 count 变量加载到 CPU 寄存器
  2. 在寄存器中执行 +1 操作
  3. 将结果写回内存

在这个流程中多个线程同时调用 addOne 方法,线程 A 读取 count 值为 0,线程 B 读取 count 值也是 0

线程 A 执行完毕写回内存 count 值为 1,线程 B 执行完毕写回内存 count 值也为 1。我们期望这个值应该为 2 。

看上去似乎是可见性的问题 volatile 关键字修饰后能否解决这个问题。

其实是不能的,volatile 能解决一部分问题,它解决不了线程切换的问题。需要通过互斥锁来解决,在 Java 中即 synchronized

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dying 搁浅

两杯酒,一杯敬你余生多欢喜。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值