因为存在重排序的过程所以我们要解决2个问题

  1. 编译,运行时重排序对编程的影响
  2. 内存可见性

编译,运行时重排序对编程的影响

重排序规则

  • 对没有依赖关系的代码,允许重排序。
    • 如: a=1;b=1。
  • 对于有依赖关系的代码,不允许重排序
    • 如:a=1;b=a 。b依赖a,不允许重排序:

as-if-serial语义

对于单线程来说,cpu可以保证程序执行的结果(可能经过重排序)和顺序执行代码的结果一致。编译器、和处理器进行重排序时都满足as-if-serial语义

在单线程中满足as-if-serial语义,那在多线程中就不同了!!!!!!

  • 多线程中如果有依赖关系,是无法做到as-if-serial语义的。
  • 要想让多线程按照自己的想法执行,只能对多线程共享的数据加锁,所有线程

内存可见性

单线程,单cpu

  • 整个程序运行在同一个CPU中,使用同一份缓存,同一份内存
    • 不存在可见性问题
    • 不存在重排序带来的问题。(因为单线程下编译器,处理器重排序时会保证as-if-serial语义)

多线程,单cpu

  • 整个程序运行在同一个CPU中,使用同一份缓存,同一份内存
    • 不存在可见性问题
    • 但会有重排序问题。(编译器,处理器不保证多线程间的as-if-serial语义)

假如cpu按如下顺序执行指令

  1. 线程1开始,执行:obj.ready=true
    1. obj.ready=true;obj.data=1之间没有数据依赖关系,可以被重排序,可能按任意顺序执行
  2. 线程1暂停,线程2被调度,执行:if (obj.ready)
  3. 线程2被调度,执行:doSome(obj.data)问题来了,线程1还没有初始化obj.data呢,就被线程2使用了

要解决这个问题,必须要有一种策略保证obj.ready=true 比 obj.data=1先执行这个机制就是内存屏障

内存屏障

  • 数据依赖屏障
    • 数据依赖屏障是read屏障的一种较弱形式。数据依赖屏障仅保证相互依赖的load指令上的偏序关系,不要求对store指令,无关联的load指令以及重叠的load指令有什么影响。
  • write(或叫store)内存屏障
    • write内存屏障保证:所有该屏障之前的store操作,看起来一定在所有该屏障之后的store操作之前执行。write屏障仅保证store指令上的偏序关系,不要求对load指令有什么影响。
  • read(或叫load)内存屏障
    • read屏障是数据依赖屏障外加一个保证,保证所有该屏障之前的load操作,看起来一定在所有该屏障之后的load操作之前执行。
  • 通用内存屏障
    • 通用屏障确保所有该屏障之前的load和store操作,看起来一定在所有屏障之后的load和store操作之前执行。

java中很多关键字的特性就是通过封装内存屏障实现的

  • voliate

更多参考:http://ifeve.com/linux-memory-barriers/#memory-barriers

多线程,多cpu

  • 整个程序运行在同多个CPU中,使用同多个缓存,同一份内存
    • 存在可见性问题
    • 存在重排序问题(解决方式和“多线程,多cpu”章节相同,不再重复)

引起内存可见性的原因,cpu、线程都有自己的缓存区,更新的变量没有及时刷新到共享内存,导致其他线程不可见

如下图,要保证程序正常执行:

  1. 线程1的2行代码,必须按顺序调度到cpu1中执行
  2. cpu将执行结果按顺序写回到线程1的私有内存区
  3. 线程1将新值,按顺序写会共享内存
  4. 线程2从主内存按顺序读取最新的值
  5. 将最新值按顺序被cpu2处理

看看需要哪些技术可以保证以上5各步骤正常运行

  • 写内存屏障可以保证1,2(按顺序让cpu计算,并按顺序写回)
  • java volite关键字,可以强制刷新线程私有内存中被修改的值到主内存
  • java volite关键字,可以强制使用时,每次必须从主内存重新读取
  • 读内存屏障可以保证,数据按照指定顺序,输送到cpu执行

参考:http://blog.csdn.net/dlutbrucezhang/article/details/10170649