并发编程Bug的源头-原子性

因为IO太慢,早期的操做系统就发明了多进程,即使在单核的CPU上咱们也能够一边听着歌,一边写Bug,这个就是多进程的功劳。操做系统容许某个进程执行一小段时间,例如50毫秒,过了50毫秒操做系统就会从新选择一个进程来执行(咱们称为“任务切换”),这个50毫秒称为“时间片”。程序员

在一个时间片内,若是一个进程进行一个IO操做,例如读个文件,这个时候该进程能够把本身标记为“休眠状态”并出让CPU的使用权,待文件读进内存,操做系统会把这个休眠的进程唤醒,唤醒后的进程就有机会从新得到CPU的使用权了。这里的进程在等待IO时之因此会释放CPU使用权,是为了让CPU在这段等待时间里能够作别的事情,这样一来CPU的使用率就上来了;此外,若是这时有另一个进程也读文件,读文件的操做就会排队,磁盘驱动在完成一个进程的读操做后,发现有排队的任务,就会当即启动下一个读操做,这样IO的使用率也上来了。早期的操做系统基于进程来调度CPU,不一样进程间是不共享内存空间的,因此进程要作任务切换就要切换内存映射地址,而一个进程建立的全部线程,都是共享一个内存空间的,因此线程作任务切换成本就很低了。现代的操做系统都基于更轻量的线程来调度,如今咱们提到的“任务切换”都是指“线程切换”。Java并发程序都是基于多线程的,天然也会涉及到任务切换,也许你想不到,任务切换居然也是并发编程里诡异Bug的源头之一。任务切换的时机大多数是在时间片结束的时候,咱们如今基本都使用高级语言编程,高级语言里一条语句每每须要多条CPU指令完成,例如count += 1,至少须要三条CPU指令。指令1:首先,须要把变量count从内存加载到CPU的寄存器;指令2:以后,在寄存器中执行+1操做;指令3:最后,将结果写入内存(缓存机制致使可能写入的是CPU缓存而不是内存)。操做系统作任务切换,能够发生在任何一条CPU指令执行完,是的,是CPU指令,而不是高级语言里的一条语句。对于上面的三条指令来讲,咱们假设count=0,若是线程A在指令1执行完后作线程切换,线程A和线程B按照下图的序列执行,那么咱们会发现两个线程都执行了count+=1的操做,可是获得的结果不是咱们指望的2,而是1。面试

非原子操做的执行路径示意图咱们潜意识里面以为count+=1这个操做是一个不可分割的总体,就像一个原子同样,线程的切换能够发生在count+=1以前,也能够发生在count+=1以后,但就是不会发生在中间。咱们把一个或者多个操做在CPU执行的过程当中不被中断的特性称为原子性。CPU能保证的原子操做是CPU指令级别的,而不是高级语言的操做符,这是违背咱们直觉的地方。所以,不少时候咱们须要在高级语言层面保证操做的原子性。编程

推荐阅读

看完这6小时的多线程与高并发视频,我面试再也没虚过缓存

价值2W多的程序员职业规划,真香多线程

看完三件事❤️

========并发

若是你以为这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:ide

 点赞,转发,有大家的 『点赞和评论』,才是我创造的动力。高并发

 关注公众号 『 Java斗帝 』,不按期分享原创知识。职业规划

 同时能够期待后续文章ing?spa

相关文章
相关标签/搜索