为什么会有并发的问题?

本文探讨了并发编程中的三大问题:可见性、原子性和有序性。由于CPU缓存的存在,线程间无法即时看到共享变量的更新,导致可见性问题。而多线程环境下,原子性问题源于线程切换可能中断操作。同时,编译器和处理器的优化可能导致指令重排序,破坏有序性。下文将介绍Java内存模型如何解决这些问题。
摘要由CSDN通过智能技术生成

当设计到并发编程的时候,通常要考虑的三个问题就是可见性原子性有序性这个问题。

可见性

一个线程对共享变量的修改,另一个线程能够立刻看到,称为可见性

为了合理利用CPU资源,CPU增加了缓存,用来均衡和内存速度的差异。正是由于CPU缓存的存在才导致了可见性问题。所以要理解可见性问题,我们需要先了解CPU的结构,下图是一个2核4线程的CPU结构图,每个物理核都会有自己的L1 Cache、L2 Cache。所有的物理核共用L3 Cache。CPU结构

  • 在Windows电脑上可以在任务管理器界面查看L1、L2、L3的大小
    Windows查看CPU缓存大小
  • Linux下查看L1、L2、L3大小
    Linux查看CPU缓存大小
    cat /sys/devices/system/cpu/cpu0/cache/index1/size
    cat /sys/devices/system/cpu/cpu0/cache/index2/size
    cat /sys/devices/system/cpu/cpu0/cache/index3/size
    

那么现在问题来了,如下代码。两个线程对共享变量count各累加1000次,我们需要的是count经过共2000次累加之后变成2000,但是代码执行的结果是小于等于2000的。

public class Problem {
    
    private static int count = 0;
    
    private static void add() {
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                add();
            }
        });
        Thread threadB = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                add();
            }
        });
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
        System.out.println(count);
    }
}

可见性代码执行结果
其实造成这种结果的原因就是每个线程可能在不同CPU核上执行,然后从内存中读取了变量count到CPU缓存,累加之后,CPU缓存不会立即更改过后的值刷回内存,这样就导致其他其他线程读不到最新的值

原子性

一个或多个操作在CPU执行的过程中不被中断的特性称为原子性

由于操作系统层面增加了线程、分时复用CPU,所以一个线程执行过程中,CPU时间片用完之后发生了线程切换,CPU交由其他线程来使用,这个时候就会存在原子性的问题。

比如count += 1编译成CPU指令:

  • 指令1:将变量count从内存加载到CPU的寄存器
  • 指令2:寄存器中执行+1操作
  • 指令3:将结果写入内存(缓存机制可能写入的是CPU缓存)
    原子性问题

有序性

代码按你写的顺序执行叫有序性

但是往往为了提高执行程序的性能,编译器和处理器会对指令进行重排序。

  • 编译器重排序:不改变单线程程序语义的前提下重新安排语句的执行顺序(编译器)
  • 指令级重排序:如果指令之间不存在数据前后关系的依赖,处理器可以语句对应的机器指令的执行顺序(处理器)
  • 内存系统重排序:因为处理器使用了缓存和读写缓冲区,导致加载和存储看上去可能是乱序的(处理器)
    指令重排序综上,为了平衡CPU和内存速度的差异,CPU引入了缓存,提高了CPU利用率,也带来了可见性问题;多核CPU架构,线程的引入和CPU分时复用技术,导致线程切换,带来了原子性的问题;编译优化,指令并行执行优化带来了有序性的问题。
    下一篇文章将会介绍JMM针对三种问题的解决方法。

代码入口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

韦韦韦韦韦韦

为知识付费

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

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

打赏作者

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

抵扣说明:

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

余额充值