并发编程

    技术2022-08-14  78

    并发编程Bug的源头

    并发编程简史面对的问题弥补速度差异的方式 并发编程问题三个核心问题可见性问题原子性问题有序性问题

    并发编程简史

    面对的问题

    CPU、内存、I/O设备之间速度存在差异

    弥补速度差异的方式

    CPU增加缓存——均衡内存与CPU之间的速度差异操作系统增加进程、线程——分时复用CPU,进而均衡CPU与I/O设备的速度差异(当进程在请求I/O资源时,进程挂起,其他进程使用CPU资源——因线程更为轻量,现在提到的“”任务切换”,主要是指“线程切换”)编译程序优化指令执行次序——使得缓存能够得到更加合理地使用

    并发编程问题

    三个核心问题

    可见性问题原子性问题有序性问题

    可见性问题

    诱因: CPU缓存 + 多核CPU解释: 为了弥补CPU与内存之间的速度差异,在CPU中添加缓存,以提升CPU的效率问题:造成了同一逻辑资源在不同CPU中值不同,CPU缓存之间、甚至内存之间同一逻辑资源同一时刻有多个值

    原子性问题

    诱因:分时复用解释:因为CPU的运行速度远高于I/O的访问速度,所以当进程需要调用I/O资源的时候会被挂起,等待请求的外部数据返回,并将CPU资源让渡给其他进程,以提高CPU资源的利用率问题:原本一个进程在运行结束前始终占有资源,但引入时间片的理念之后,一个进程在多次挂起、唤醒之后才能完成原本一次性执行的事情,破坏了线程的原子性,在此期间,上一个线程使用的资源会被其他进程使用(java并发程序都是基于多线程的)

    有序性问题

    诱因: 编译优化解释:为了优化性能,编译器会改变程序中语句的先后顺序问题:使用java编写的程序中的一条语句,在编译器看来可能是多条命令的集合,编译器保证了一条java语句执行的最终实现,但在并发情况下,线程间在一条java语句中也会产生影响,编译器的编译优化原则反而使情况更为混乱示例:(java编程中利用双重检查创建单里对象) public class Singleton { static Singleton instance; static Singleton getInstance(){ if (instance == null) { synchronized(Singleton.class) { if (instance == null) instance = new Singleton(); } } return instance; } }

    理想的new操作执行顺序:

    分配一块内存 M在内存M上初始化 Singleton 对象将 M 的地址赋值给 instance 变量

    实际的new操作执行顺序:

    分配一块内存 M将 M 的地址赋值给 instance 变量在内存M上初始化 Singleton 对象

    可能的场景:

    当线程A在new的过程中,执行完了优化后的第2个步骤后,恰好发生了线程切换(同时包含原子性被破坏的问题 和 不同CPU间资源不可见的问题) 线程B运行到第一个instace的判空操作时,instance已经指向了M的地址,不会进入创建程序 此时线程B访问instance,会抛出空指针异常

    Processed: 0.016, SQL: 9