【程序员的必修课】并发编程--理论基础

并发编程-理论基础

一、前言

很多小伙伴学习并发编程,上来就是看 JUC 包,背面试题,但是对并发编程的底层原理不甚了解,导致写出的程序出现奇怪的问题,也没有足够的理论支撑去排查,说实话,我一开始也是这样的。但是随着学习的深入和面试的经历,我越发了解到系统学习并发编程的重要性。

从今天开始,我就带领小伙伴们,从理论触发,逐渐吃透并发编程

俗话说得好,基础不来,地动山摇,在学习并发编程之前,我们要先了解并发编程解决的原因、其引发非问题

这里要提醒一句,并发编程和 JMM (内存模型)密不可分,小伙伴在学习之前,可以先去复习一下 JMM 的知识哦

二、理论基础

1、为什么需要多线程

众所周知,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:

  • 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异(在耗时的 IO 执行的时候,可以切换线程执行其他任务,这是多线程出现的最重要的原因

这种方式导致原子性问题

  • CPU 增加了缓存,以均衡与内存的速度差异(CPU 寄存器,一级缓存,二级缓存…其均衡与内存的差异的主要做法,是先将修改写入缓存中,然后在一定时间后,才会将缓存内容写入内存,这样,可以将多次对内存的修改,打包成一次进行修改,减少对内存总线的占用)

导致 可见性问题

  • 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用

导致有序性问题

2、并发问题的根源

可见性、原子性、有序性

1)可见性

可见性,是由于 CPU 缓存导致的

我们通过下面的例子来看看具体可见性问题的成因

//线程1执行的代码
int i = 0;
i = 10;
 
//线程2执行的代码
j = i;

假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中

此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10

这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值

可见性问题

2)原子性

数据库里,我们有 ACID 四个事务的性质,其中 Atomicity,及原子性,它表示的意思是对于同一个事务,在提交之前,其内部的操作要么全部成功,要么全部失败

并发编程中的原子性问题,其实和这个差不多,比如说我们希望一个线程,可以处理几个任务,而不被其他线程打扰

**经典的问题:**写一个方法,模拟账户A 向账户B 转账

最最简单的想法,是这样的:

public boolean transform(Account a,Account b) {
   
  synchronized(a) {
   
    synchronized(b) {
   
      // 执行转账操作
    }
  }
}

这个写法,有一个致命的问题,就是有可能发生死锁。假如在线程 a 想向线程b 转钱的过程中,线程b 又向线程a 转钱,就可能出现 a 在等待获取资源b,b 在等待获取资源 a 的尴尬局面

有小伙伴可能会说了:"那么我们给方法加一个 synchronized 锁不就好了"

public synchronized boolean
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FARO_Z

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值