原子性操作的实现


原子性操作的实现

CPU原子性操作的实现

使用总线锁保证原子性:

总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号的时候,其他处理器的请求将会被阻塞住,那么该处理器就可以独享内存

通过缓存锁定来保证原子性:

内存区域如果被缓存在处理器的缓存行,并且在Lock操作期间被锁定,当它执行锁操作回写内存的时候,处理器不在总线上声称LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性。缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域,当其他处理器回写已经被锁定的缓存行数据时,会使缓存行失效

不适用缓存锁定的情况

  • 操作的数据不能被缓存到处理器内部,或者操作的数据跨多个缓存行
  • 不支持缓存锁定的处理器

Java原子性操作的实现

CAS(Compare and Swap)

CAS思想

当线程拿到一个值并对其修改,然后写入到内存中时。在写入之前,会先拿到当前内存中的值,然后与之前拿到的旧值进行对比。如果没有变化,则写入新的值。如果有变化,则使用新拿到的值进行修改
这一个行为称之为CAS操作。

CAS操作会不断轮询来判断旧值是否被修改,我们也称之为自旋CAS。

Java是如何实现CAS的

Java中CAS操作的执行依赖于Unsafe类的方法。Unsafe类处于sun.misc.Unsafe类中的中。通过调用UnSafe类中CAS方法,JVM会帮我们实现CAS汇编指令。由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe的方法也都是native的方法,基于该类可以直接操作特定内存的数据,其内部方法操作可以像C的指针一样直接操作内存.

CAS引发的问题

  1. 自旋CAS如果长期不成功,那么会不断地轮询,这样会给CPU带来非常大地执行开销。
    1. 可以通过延迟流水线执行指令,使CPU不会消耗过多地资源,延迟时间取决于实现
    2. 避免在退出循环地时候因内存顺序冲突而引起地CPU流水线被清空,从而调高CPU的执行效率

      内存顺序冲突:多个CPU同时修改同一个缓存行的不同部分引起其中一个CPU的操作无效.当出现这个内存顺序冲突时,CPU会必须清空流水线

  2. 只能保证一个共享变量的原子操作
    1. Java1.5后,引入了AtomicReference类保证引用对象之间的原子性
  3. ABA问题:CAS只判断最终结果,保证了新值和旧值之间的正确性,但是旧值可能经过一系列操作之后,没有发生改变.比如50->100->50.CAS只关注一头一尾.
    1. 解决方式是通过版本号,除了检查新值和旧值的正确性以外,还需要判断版本是否是一致的

文章作者: 彭峰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 彭峰 !
  目录