因为存在重排序的过程所以我们要解决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开始,执行:obj.ready=true - obj.ready=true;obj.data=1之间没有数据依赖关系,可以被重排序,可能按任意顺序执行 - 线程1暂停,线程2被调度,执行:if (obj.ready) - 线程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 {{:pasted:20160507-130154.png}} ====多线程,多cpu==== * 整个程序运行在同多个CPU中,使用同多个缓存,同一份内存 * 存在可见性问题 * 存在重排序问题(解决方式和“多线程,多cpu”章节相同,不再重复) 引起内存可见性的原因,cpu、线程都有自己的缓存区,更新的变量没有及时刷新到共享内存,导致其他线程不可见 如下图,要保证程序正常执行: - 线程1的2行代码,必须按顺序调度到cpu1中执行 - cpu将执行结果按顺序写回到线程1的私有内存区 - 线程1将新值,按顺序写会共享内存 - 线程2从主内存按顺序读取最新的值 - 将最新值按顺序被cpu2处理 {{:pasted:20160507-144620.png}} 看看需要哪些技术可以保证以上5各步骤正常运行 * 写内存屏障可以保证1,2(按顺序让cpu计算,并按顺序写回) * java volite关键字,可以强制刷新线程私有内存中被修改的值到主内存 * java volite关键字,可以强制使用时,每次必须从主内存重新读取 * 读内存屏障可以保证,数据按照指定顺序,输送到cpu执行 参考:http://blog.csdn.net/dlutbrucezhang/article/details/10170649 ----