并发编程笔记1:并发bug的成因

简介: 并发编程笔记1:并发bug的成因

前言:

    最近在学习并发编程的知识,打算好好学习下并发这块,之前有处理过并发的问题,但是学的不够体系,知识比较零散.
所以买了极客时间的并发课程(java并发编程实战)和《并发编程实战》从头系统化的学一遍。这里记录一下自己的学习过程和心得之类的。

    首先咱们得知道为啥会产生并发编程的bug,究其根本原因是因为cpu处理速度>>内存>>I/O,这里用了">>",是远远大于的意思,极客时间里面的例子更形象说是
天上一天,人间一年。天上指的是cpu对于内存,内存对于I/O.用来凸显其处理速度的差距。因为有了这三者的差距,cpu的利用率就比较低,一直得等待内存,I/O处理完。
所以为了减少这三者的差距,做出了三种优化:
1.CPU 增加了缓存,以均衡与内存的速度差异;
2.操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;
3.编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。

这三类优化相应的耶带来了三种问题,都可能会导致并发问题:

1.缓存的可见性问题:
    上面的第一种优化,在cpu和内存之间增加了缓存,在多核cpu的情况下,每个cpu可能都有自己的缓存。现在假设一个value=0,当线程a在cpu1的缓存中给value加1,线程b在cpu的缓存中给value加1,它们再往内存中刷新value的值的时候,可能顺序并不是我们想的value变为2.
可能的执行过程是线程a,b如果都将value从内存中读入到自己的cpu缓存中,线程a先给value加了1,然后刷新到内存中value变为1,此时线程b也将value加1,刷新到内存中,覆盖了a写入的值,value还是1.这就是缓存的可见性问题,各自的cpu缓存并不知道,其他线程也对相同的值做了操作。
比较经典的例子就是,启动多个线程对同一个变量进行增加操作,得出来的值不是预想的值。

2.线程切换导致的原子性问题:
    这个问题其实比较好理解,我们学过操作系统知道,多进程操作系统在执行任务的时候,不是串行的进程a执行完,再去执行进程b,而是每个进程有一个操作系统分配的执行时间(称为是时间片),当进程a时间片消耗完了,
就切换到另一个进程。这样进程之间交替的执行,又因为时间片的时间非常短,用户是感知不出来这种切换的。介绍完进程的切换,咱们再说说像java这种的高级语言,对应的底层操作系统指令可能是好多条。
比如我们想的count+=1,对于操作系统就不是原子操作,可能是1,2,3...好几条,那么有两个线程都对count加1的时候,如果进程a中count的值还没有加完,此时操作系统给进程a的时间片用完了,切换到进程b,这时候进程b读到的还是没有增加的的值,那么计算结果就不符合我们的预期。

3.编译优化带来的指令执行次序问题:
    我们写java的知道volatile关键字有使变量可见和禁止指令重排序的作用,其实就是针对的这个问题。编译器的优化能大大提高执行效率,但是也会带来一些问题,“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;
  }
}

这是双重校验锁的单例,代码执行顺序是
1.分配一块内存M;
2.在内存M上初始化 Singleton 对象;
3.然后M的地址赋值给 instance 变量。
可能会被优化成:
1.分配一块内存M;
2.将M的地址赋值给 instance 变量;
3.最后在内存M上初始化 Singleton 对象。
这样就可能会导致下面这种情况,线程a调用getInstance(),a如果处于被优化的步骤2,然后时间片用完了,此时线程b进来,执行到第一个instance == null这里,发现instance不为null,就返回了未初始化的instance。此时访问instance对象可能抛出空指针异常。

相关文章
|
7月前
|
人工智能 数据可视化 开发者
AI生成网站怎么做?3步快速搭建一个简单官网
很多人因技术门槛放弃建站?AI生成网站让零代码建站成为可能:只需三步——明确需求、AI自动生成结构、简单调整上线。工具如lynxcode可快速搭建官网/作品集,省去开发、部署烦恼,特别适合个人和小团队低成本高效启动项目。
|
7月前
|
JavaScript 芯片
三节串联锂电池充电芯片应用与PCB设计指南
PW4053A(异步)是一款5V输入,最大1.2A充电电流(是指电池端的电流,输出12.6V电池端的电压),支持三节锂电池的升压充电管理IC。PW4053A集成功率MOS采用异步开关架构,使其在应用时仅需极少的外围器件,可有效减少整体方案尺寸,降低BOM成本。PW4053A的升压开关充电转换器的工作频率为500KHz,转换率微90%。PW4053A啊呼入电压为5V,内置自适应环路,可智能调节充电电流大小,防止拉垮适配器输出,可匹配所有适配器。PW4053A提供SOP8-EP封装形式,工作温度额定范围为-40℃至85℃。
|
8月前
|
云安全 数据可视化 安全
企业官网搭建_SAAS建站和CMS系统建站如何选型?
优质企业官网是品牌门户与增长引擎。建站主流分SAAS(腾讯/阿里/华为云建站,适合轻量、快速上线的小企业)和CMS系统(如PageAdmin、WordPress,适配中长期运营、私有部署、国产化、外贸等多元需求)。选型需匹配发展阶段与核心诉求。
776 10
|
9月前
|
人工智能 自然语言处理 安全
2026年适合电商的高性价比智能客服系统推荐
电商智能客服已成服务核心,本文对比瓴羊Quick Service、亿捷云客服、美洽AI客服、探域智能体四大产品,在全渠道整合、AI协同、成本控制等方面深度解析,为不同规模电商提供选型指南,助力降本增效与体验升级。
|
Kubernetes Java 调度
接入 MSE XXL-JOB 任务调度实现优雅下线
阿里云 MSE XXL-JOB 产品最新版本特别新增了分布式定时任务优雅下线功能,能有效保障高频业务处理在发布与重启过程中的平滑持续,避免数据丢失和调度失败。
787 119
|
存储 安全 API
73_安全配置:LLM开发环境的全面防护指南
在2025年的AI开发环境中,大型语言模型(LLM)已成为核心技术,但伴随其广泛应用的是日益严峻的安全挑战。据统计,2025年第一季度发生的AI安全事件中,LLM环境配置不当导致的漏洞占比高达43%,造成的损失超过2.1亿美元。本文将深入探讨LLM开发环境的安全配置最佳实践,帮助开发者构建一个安全、可靠的开发环境。
1013 0
|
网络协议 安全 Devops
Infoblox DDI (NIOS) 9.0 - DNS、DHCP 和 IPAM (DDI) 核心网络服务管理
Infoblox DDI (NIOS) 9.0 - DNS、DHCP 和 IPAM (DDI) 核心网络服务管理
775 4
|
存储 算法 数据安全/隐私保护
密码如何存储?
本文介绍了密码存储的安全策略及加密算法对比。内容涵盖明文密码风险、彩虹表攻击原理,以及如何通过加盐、迭代哈希提升安全性,推荐使用 BCrypt 算法。同时比较了 DES、AES、SM4 对称加密算法,和 RSA、ECDSA、SM2 非对称加密算法的特性与应用场景。
772 0
|
Ubuntu 大数据 Linux
大数据入门系列 2:全网最全,VMware 虚拟机上安装 Ubuntu 完整步骤及需要注意的问题
大数据入门系列 2:全网最全,VMware 虚拟机上安装 Ubuntu 完整步骤及需要注意的问题
2741 1
大数据入门系列 2:全网最全,VMware 虚拟机上安装 Ubuntu 完整步骤及需要注意的问题
|
存储 Java 调度
FileInputStream,FileOutputStream 和 FileReader ,FileWriter 类的基本使用【 File类+IO流知识回顾②】
这篇文章回顾了Java中FileInputStream、FileOutputStream、FileReader和FileWriter类的基本使用方法,包括读取和写入文件的操作,以及字符流和字节流的区别和应用场景。
FileInputStream,FileOutputStream 和 FileReader ,FileWriter 类的基本使用【 File类+IO流知识回顾②】