CAS操作

来源:互联网 发布:收购淘宝店铺的网站 编辑:程序博客网 时间:2024/05/17 06:24


1.在说CAS操作的开始,我们先了解一些术语

这里写图片描述

这里写图片描述

2.处理器是如何实现原子操作

32位IA-32处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存中读取或写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。

2.1 总线锁定

这里写图片描述

如果多个处理器一起对共享变量进行读改写操作(i++就是典型的读改写操作),这个读改写操作就不是原子的。如上图所示,各个处理器将i的值读入自己的处理器缓存中,各自对各自的缓存里的i值进行操作,然后分别写入系统内存从而导致了问题的产生。要想对共享变量的读改写操作也是原子性的,必须保证,CPU1读改写共享变量的时候,CPU2不能操作该共享变量内存地址的缓存行。

处理器的总线锁就是这么来保证原子性的,所谓总线锁就是使用处理器提供的一个LOCK #信号,当一个处理器在总线上输出此信号时,其他处理器的请求将会被阻塞住,那么该处理器可以独占共享内存。

2.2 缓存锁定

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

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

缓存锁是:一个处理器的缓存写回到内存会导致其他处理器的缓存无效,在多核处理器中,例如在Pentium和P6 family处理中,如果通过嗅探一个处理器来检测到其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。

但是有两种情况是不能使用缓存锁:一是不能缓存到处理器的数据以及跨多个缓存行的数据;而是有些处理器不支持缓存锁定。

3.在Java中可以使用锁和循环CAS来进行原子操作

自旋CAS的基本思路就是循环进行CAS操作,知道成功为止。

在JDK1.5之后,JDK的并发包里提供了一些类来支持原子操作,如AtomicBoolean,AtomicInteger,AtomicLong都是用原子的方式来更新指定类型的值。例如AtomicInteger的用法如下:

 AtomicInteger atomicInteger=new AtomicInteger(0); int i=atomicInteger.get(); atomicInteger.compareAndSet(i,i++);

CAS还存在以下三个问题:

(1)ABA问题。如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时发现它的值没有发生变化,但实际上却发生了。ABA的解决思路就是使用版本号,在变量前面追加版本号,那么A——B——A 就变成了1A——2B——3A。
(2)循环时间长开销大
(3)只能保证一个共性变量的原子操作。也就是多个共享变量操作时,循环CAS就无法保证操作的原子性了。但从JDK1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。

原创粉丝点击