并发编程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,会抛出空指针异常