为什么会出现并发问题,可能是因为这些年,存储设备和Cpu的不断发展

Cpu的速度已经到了一种可怕的速度,整个主机之中,Io设备 Cpu 内存三者的关系可以为

Cpu一秒,内存一分钟,内存一秒,Io一分钟

为了满足水桶效应,即Io和内存可以跟上Cpu的速度,则做了以下的优化

1.Cpu增加了缓冲,用来均衡差异

2.增加了线程和进程,来分时使用Cpu,均衡差异

3.编译程序优化了指令执行次序,使得缓冲能够合理的运用

故,并发问题的源头,在于此

CPU增加了缓存导致的可见性问题

何为可见性:

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

在单核的时代,所有的线程都在一颗CPU上执行,故在此CPU上,一个线程的执行必然是对自己是可见的

而且只要是单核的状态下,无论多少个线程操作其中的缓存,得到的都是CPU最新的值(因为不存在CPU之间的值同步)

图片

在多核的时代,每个CPU都有自己的缓存,这时候对于可见性的保证就比较困难了

多个线程在不同的CPU上执行,操作的是不同的CPU的缓存

图片

这就带来了多核情况下的可见性问题

增加了线程,导致了线程切换带来的原子性问题

无论在计算的什么时代中,都有着多进程的功能,也就是一遍写着代码,一遍听着歌

这是因为操作系统允许某个进程执行一小段时间,执行完成,再进行切换,这个时间称为时间片

图片

比如说一个场景,一个进程要去执行IO操作,这时候就会把自己标为休眠状态,让其他线程进行执行,

自己去执行IO工作,这样能保证CPU的利用率到达近乎百分之百

这就需要一个队列,保存了接下来Cpu执行的工作,从而达到这个Cpu线程执行完成了,下个线程补上的情况

在Java中常见的并发编程问题就出在线程切换的时候

为什么会怎么说

何必如一个CPU指令 count+=1,实际的CPU指令为

count从内存加载到CPU中

count+1

count将值写入内存或者缓存中

在操作系统执行过程中,可能发生在任何一个CPU指令中,就是上面说的任何一个汇总

故,执行两次count+=1,可能会出现结果为1的情况

故,经常把一个或者多个操作在Cpu执行过程中不被中断的特性称为原子性,Cpu保证Cpu指令的操作是原子性的

但是我们实际需要保证的原子性需要建立在高级语言层面

指令优化,带来的编译优化有序性问题

在Java中,通常会存在自带的程序优化,即为 程序汇总 a=6,b=7,编译器优化后可能变成 b=7  a=6,虽然不会影响到结果,

但在并发编程中,可能导致意想不到的bug

举一个简单的例子

通常来说一个对象的创建,在理论上来说应该为

分配一块内存M

M上初始化对象

将M和变量挂上联系

初始化完成

但是编译器优化过后,会变为

分配一块内存空间M

将M和变量进行挂上联系

M上初始对象

初始化完成

图片

这就出现了一个bug问题,即为在并发获取实例的时候,可能会出现获取到的对象是一个null,

这是获取到的对象可能只是挂上了联系,并没有初始化对象

虽然缓存,线程切换,编译优化都是为了更好的服务开发者,但是使用一个新的技术必然会带来对应的新的问题,需要开发人员更加的小心

发表评论

邮箱地址不会被公开。 必填项已用*标注