反骨之硬件&软件为Java并发编程中挖的坑(可见性&原子性&有序性)

前言

本篇博文,主要集中并发编程的三个问题:可见性、原子性、有序性。数据库

只讲理论,不谈如何解决。编程

说白了并发编程中的三个问题,就是先辈们给咱们这些后辈留下来的坑!缓存


那么,先辈们给并发编程带来什么坑?

相信大部分人都知道,一台计算机的组成包括但不限于:CPU、内存、显卡….安全

在这里, 笔者将计算机中的组抽象成几类,即:CPU、I/O设备、内存微信

有了以上三个分类,笔者接下来的文章就好写了。(滑稽)多线程

  • Cpu多核缓存带来的数据----可见性问题(硬件):(建议直接跳到小节末尾, 看结论便可)

    讲道理,在处理速度方面CPU >> 内存 >> I/O设备并发

    这时候, 为了充分发挥CPU的计算速度, 硬件方面则是在CPU和内存之间, 增长了一种叫高速缓存的技术。学习

    总而言之,高速缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾优化

    固然, 在单核时代Cpu缓存并无出现数据一致性问题, 可是在多核时代就不同了。操作系统

    企业微信20190810021224.png

    在多核处理器中,因为Cpu并不与内存直接打交道, 而是经过高速缓存。

    同理, 内存也是并不直接与Cpu打交道,也是经过高速缓存与Cpu打交道。

    • cpu <——> 高速缓存 <———> 内存

    缓存一致性问题以及解决方案,网上一搜一大堆,笔者在此就不作过多的叙述。

    因此,只须要记住结论便可:

    Cpu缓存不一致为并发编程带来----可见性问题。(结论)


  • 操做系统中进程&线程上下文切换给并发编程带来操做----原子性问题:

    在之前的印象中,说到原子性,笔者张口就是:原子操做是一个不可分割的总体, 该总体中的全部指令,要么所有执行, 要么所有不执行, 没有中间状态。在此,笔者所写的原子性概念,仿佛过于宽泛和缥缈。并且,原子性在不一样的使用场景,也有可能含义并不相同。

    接下来,咱们试着从数据库事务并发编程两个方面来进行对比:

    在数据库中,原子性概念以下:

    • 事务被当作一个不可分割的总体,包含在其中的操做要么所有执行,要么所有不执行。且事务在执行过程当中若是发生错误,会被回滚到事务开始前的状态,就像这个事务没有执行同样。

    在并发编程中,原子性概念以下:

    • 第一种理解:一个线程或进程在执行过程当中,没有发生上下文切换
      • 上下文切换:指CPU从一个进程/线程切换到另一个进程/线程(切换的前提就是获取CPU的使用权)。
    • 第二种理解:咱们把一个线程中的一个或多个操做(不可分割的总体),在CPU执行过程当中不被中断的特性,称为原子性。(执行过程当中,一旦发生中断,就会发生上下文切换)

    从上文中能够看出,并发编程和数据库二者之间的原子性概念有些类似。

    都是强调,一个原子操做不能被打断!!

    因此,上下文切换给并发编程带来——原子性问题。(结论)


  • 编译器的编译优化给并发编程带来程序----有序性问题:

    讲道理,在计算机语言中,高级语言的运行都须要经过编译器转换成机器语言,经过机器语言在计算机上运行。

    那么,在编译器中为了提升运行速度,会对内存访问的有关操做(读&写)作一种优化,即指令的重排序。这种重排序在单线程环境下,不影响结果的正确性。可是,在多线程环境下可能会对结果的正确性产生影响。

    指令的重排序,是形成并发编程的有序性问题的缘由。

    编译器带来指令重排序,而指令重排序形成有序性问题。

    编译器优化—带来—> 指令重排序—带来—>有序性问题

    因此,编译器的优化给并发编程带来—有序性问题!

总结

  • 多核Cpu的高速缓存为并发编程带来—可见性问题。
  • 线程的切换给并发编程带来—原子性问题。
  • 编译器给并发编程带来—有序性问题
  • 不论是可见、原子仍是有序性,都是线程安全问题的表现形式。

最后,本篇博文,主要是对刚学习并发编程的朋友,提出并发编程的三个问题的概念。

说实话,笔者刚开始学的时候,并无想过想过原子、有序、可见性的形成缘由,直接通通死记硬背!

有时候想一想,以为本身挺好笑的。

总之,读者们要记住,整个Java并发包的设计与实现,都是为了解决并发编程的可见、原子、有序三个问题。

接下来,笔者会尝试着写写,Java是如何解决可见性、原子性和有序性的….

相关文章
相关标签/搜索