Java的CAS原理
一、CAS原理
CAS是compare and swap,意思是比较并交换
java.util.concurrent包中借助CAS实现。
CAS操作包含三个操作数——内存位置V、预期原值A、新值B。如果内存位置和预期原值相同,那么处理器会自动的将该位置
的值更新为新值,否则处理器不做生命和操作。无论那种情况,都会在CAS指令之前返回该位置的值。
悲观锁:如:synchronized。当一个线程获得锁时,其他线程只能挂起,只能等待持有锁的线程释放锁。
乐观锁:每次不加锁,默认没有冲突而去完成某项操作,如果因为冲突失败就重试,知道成功为止。机制就是CAS。
二、CAS的目的
非阻塞算法:一个线程的失败或者挂起不应该其他线程的失败和挂起的算法。
三、问题
CAS存在三大问题。ABA问题,循环时间长开销大,只能保证一个共享变量的原子操作。
1.ABA问题。因为CAS需要在操作值得时候检查值有没有发生变化。如果没有发生变化则更新。但是一个值A变为B,又变成A,那么CAS进行检查的时候并没有发现它的变化,但实际已经发生了变化。ABA问题解决的思路是时候版本号。在变量前面加上版本号。每次变量的更新都把版本号加一。A-B-A会变成1A-2B-3A。
2.循环时间常开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3.只能保证一个共享变量的院子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
四、concurrent包的实现
concurrent根据CAS实现的模式:
1.将共享变量声明为volatile;
2.使用CASd的原子条件更新来实现线程之间的同步;
3.同时,配合volatile的读/写和CAS所具有的volatile读和写的内存语义实现线程间的通信
由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
- A线程写volatile变量,随后B线程读这个volatile变量。
- A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
- A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
- A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。