并发编程笔记(一)

三类并发问题

  • 多核CPU缓存带来的可见性问题
    线程 A 和线程 B 同时开始执行,那么第一次都会将 count=0 读到各自的 CPU 缓存里,执行完 count+=1 之后,各自 CPU 缓存里的值都是 1,同时写入内存后,会发现内存中是 1,而不是期望的 2


    ec6743e74ccf9a3c6d6c819a41e52279.png
  • CPU 线程切换导致的原子性问题
    count +=1;对应CPU的三条指令
    指令 1:首先,需要把变量 count 从内存加载到 CPU 的寄存器;
    指令 2:之后,在寄存器中执行 +1 操作;
    指令 3:最后,将结果写入内存(缓存机制导致可能写入的是 CPU 缓存而不是内存)
    如果执行序列为如下情况就会出现问题,可见count+=1 并不是原子操作,只有CPU级别的指令才是原子的


    33777c468872cb9a99b3cdc1ff597063.png
  • 编译优化带来的有序性问题
    通过双重检查单例模式的例子说明:

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

问题出现在 new Singleton()的时候,原以为的CPU指令执行顺序:
1.分配一块内存 M;
2.在内存 M 上初始化 Singleton 对象;
3.然后 M 的地址赋值给 instance 变量。
实际上经过优化后的指令执行顺序:
1.分配一块内存 M;
2.把M 的地址赋值给 instance 变量;
3.在内存 M 上初始化 Singleton 对象。
有可能出现的问题:
A线程执行完步骤2后,B线程在第一个判空语句时,instance不再是null,直接返回instance实例,但是该实例并没有经过初始化,引用成员变量时会出现空指针异常。
第一个和第三个问题可以使用volatile关键字解决,volatile可以实现资源的可见性并且可以避免指令重排序。第二个问题可以加锁。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。