【java并发编程艺术学习】(五)第二章 java并发机制的底层实现原理 学习记录(三) 原子操作的实现原理学习

章节介绍

主要包括 术语定义、处理器如何实现原子操作、Java如何实现原子操作;

原子(atomic)本意是 不能再进一步分割的最小粒子,“原子操作” 意为 不可被中断的一个或一系列操作。

 

术语定义

 

自己的理解:

  缓存行:缓存的最小操作单位。注意,缓存与内存是不一样的。

  比较并交换:结合上一篇中CAS操作的介绍进行理解。CAS操作需要输入两个数值,一个新值A,一个旧值B,在操作期间先比较旧值B有没有发生变化,没有发生变化,才交换,发生了变化,就不交换。

  CPU流水线:结合生产车间的流水线,一种设备或者工人只做一个工作,等这几个设备或者工人都工作了一遍,也就是完成了一个CPU的时钟周期。

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

 

处理器如何实现原子操作

  (1)使用总线锁保证原子性

    如果多个处理器同时对共享变量进行改写操作(经典例子 i++),那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一样。以 i++ 为例,如果我们进行两次i++操作,我们期望的值是3,但是可能是2。原因可能是 多个处理器同时从各自的缓存中读取变量,分别进行了加 1 操作,然后分别写入到系统内存中。

  那么,想要保证读改写共享变量时候的操作时原子的,就必须保证在一个处理器读改写共享变量的时候,其他处理器不能操作缓存了该共享变量内存地址的缓存。处理器使用总线索来解决这个问题。总线索 就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出该信号时,其他处理器的请求就处于阻塞,该处理器可以独占共享内存

  (2)使用缓存锁保证原子性  

    在同一个时刻,我们只需要保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间的通信锁定了,这样造成的后果就是,在总线锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大。

    频繁使用的内存会缓存在处理器的L1、L2、L3的高速缓存里,那么原子操作就可以直接在内部缓存中进行,并不需要声明总线锁。

    缓存锁定:如果内存区域被缓存到处理器的缓存行中,并且在LOCK操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言LOCK#信号,而是修改内部分存储地址,并允许他的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效。以 多个处理器 执行 i++为例,如果CPU1修改缓存行中的i时使用了缓存锁定,那么CPU2等就不能同时缓存 i 的缓存行。

    关键字:为例保证各个处理器的缓存一致、缓存一致性协议、每个处理器、嗅探、总线、数据、检查、过期、无效状态、内存、重新读取

    备注:可以结合 volatile的实现原则进行理解学习。

    特殊情况,不能使用缓存锁定:(1)当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行,则处理器会调用总线锁定;

                  (2)有些处理器不支持缓存锁定。

Java如何实现原子操作?

  在Java中可以通过  锁 和 循环CAS的方式来实现。

  (1)使用循环CAS操作实现原子操作。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止,举例:实现一个基于CAS线程安全的计数器方法。

    从Java1.5开始,JDK的并发包里提供了一些类用于支持原子操作,如 AtomicBoolean(用原子的方式更新boolean值)、Atomicinteger(用原子的方式更新int值)和AtomicLong(用原子方式更新long值)。这些包装类中还提供了有用的工具方法,比如 以原子的方式将当前值自增1 和 自减1。

  (2)CAS实现原子操作的问题

    ABA问题;循环时间长开销大;只能保证一个共享变量的原子操作。

    ABA问题说明:在CAS操作 值的时候,会检查值有没有发生变化。如果一个值原来是A,变成了B,又变成了A,这样的话,用CAS检查时候,会发现他的值没有变化,实际上却发生变化了。解决思路就是 使用版本号。每次变化,都会更新版本号:1A-->2B-->3A。

    从Java1.5开始,JDK的Atomic包里提供了一个类 AtomicStampedReference 来解决ABA问题。这个类的 compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志。如果都相等,则把该引用 和该标志的值都更新为给定的值。

    自旋CAS如果长时间不能成功,会给CPU带来非常大的执行开销。如果JVm能够支持处理器提供的pause命令,那么效率会有一定的提升。

    对一个共享变量执行操作时候,我们可以循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性了,这是就得使用 锁。取巧的方式:多个共享变量合并成一个共享变量来操作。举例:i = 2; j = a ,合并为 ij = 2a。

    从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。(备注:这里需要详细看下是如何处理的.....

  (3)使用锁机制实现原子操作

    锁机制保证了只有获取锁的线程才能操作锁定的内存区域。除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。(备注:后半部分,我还得好好理解下...

  

 

posted @ 2018-10-16 23:13  正则吃饺子  阅读(255)  评论(0编辑  收藏  举报