极客时间-java并发编程实战听课笔记(2) 可见性、原子性和有序性问题:并发编程Bug的源头

java并发编程实战听课笔记(2) 可见性、原子性和有序性问题:并发编程Bug的源头

为何会出现并发问题

根源

为了加速程序执行速度、提高资源利用率,有了各种技术,而这些技术也带来了不同的副作用:

  1. CPU有缓存,缓存导致可见性问题
  2. 操作系统有进程、线程,分时复用CPU,线程切换带来了原子性问题
  3. 编译优化,带来有序性问题

CPU缓存带来的可见性问题

如果是单核CPU时代,CPU有缓存,没什么影响,因为只有一个CPU、这个CPU里的缓存只能按顺序访问,那么程序A更新缓存值后,程序B再执行,必然能看到这个结果。
但进入多核时代,每个核的CPU都有自己的缓存,那么不同线程运行在不同CPU上,更新后的数据就不一定保证对运行在其他核的程序可见。

线程切换带来的原子性问题

操作系统分时复用CPU,做任务切换时,切换发生在CPU指令级别上,不是高级语言的一条语句上。典型例子:java中的count++;这条语句,实际上在CPU上运行时,大致会分为3条CPU指令:

将count的值从内存写入到CPU寄存器
CPU寄存器+1,得到结果
将结果写入到内存中

在这3条指令执行时,如果没有并发控制、就有可能被打断,进而导致程序出错。

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

典型案例:双重检查创建单例对象
示例:

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

问题:instance = new Singleton();这条命令实际上是分3步执行,编译优化可能导致此处instance已被赋值、但赋值的内存地址所指对象是null,若此时发生多线程并发访问,就可能出错。

总结

在采用一项技术的同时,一定要清楚它带来的问题是什么,以及如何规避。

参考文章

极客时间版权所有: https://time.geekbang.org/column/article/83682

你可能感兴趣的:(java,并发,听课笔记)